GHSA-8pqf-f4m5-798g: Medium severity pip/Twisted vulnerability
Summary wildcardToRegexp() in twisted/mail/imap4.py converts IMAP LIST/LSUB wildcard patterns to Python regular expressions. It substitutes the two IMAP wildcards ( → (?:.?) and % → (?:(?:[^\\/])?)) but passes every other character through unchanged to re.compile(). This means that an authenticated IMAP client can send a quoted pattern string containing arbitrary regex syntax - including catastrophic-backtracking constructs such as (a+)+z.
Because Twisted runs a cooperative, single-threaded reactor, a blocking regex match freezes all I/O on the server for the duration of the match.
---
Vulnerable Code
twisted/mail/imap4.py
python line 4595 def wildcardToRegexp(wildcard, delim=None): wildcard = wildcard.replace("", "(?:.?)") if delim is None: wildcard = wildcard.replace("%", "(?:.?)") else: wildcard = wildcard.replace("%", "(?:(?:[^%s])?)" % re.escape(delim)) return re.compile(wildcard, re.I) # ← user input compiled verbatim
python line 4993 class MemoryAccountWithoutNamespaces: def listMailboxes(self, ref, wildcard): ref = self.inferiorNames(parseMbox(ref.upper())) wildcard = wildcardToRegexp(wildcard, "/") # ← user-supplied wildcard return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)] ---
Proof of Concept
python from twisted.mail.imap4 import wildcardToRegexp import time
rx = wildcardToRegexp("(a+)+z", "/") for n in [20, 22, 24, 26, 28]: victim = "a" n t0 = time.perfcounter() rx.match(victim) print(f"n={n}: {time.perfcounter() - t0:.3f}s")
Output on Twisted 25.5.0:
[] Compiled regex: '(a+)+z' [] Note: metacharacters ( ) + ? are NOT escaped - they go straight to re.compile()
n time --- ---------- 20 0.153s 22 0.651s 24 2.941s 26 14.545s 28 55.019s
Timing doubles roughly every two characters (exponential growth), confirming catastrophic backtracking.
---
Impact
Twisted's reactor is single-threaded and cooperative. A blocking re.match() call suspends the entire event loop - no other connection can be accepted, read, or written while the match runs. A single authenticated LIST command with a 28-character target mailbox name can stall the server for ~55 seconds.
An attacker who can register an account (or obtain credentials through other means) can:
1. CREATE a mailbox whose name is an exponential-blowup trigger string. 2. Issue LIST "" "(a+)+z" (or equivalent ReDoS pattern). 3. Repeat at ~1-minute intervals to keep the server permanently unavailable.
No exploit code or special privileges beyond an IMAP login are required.
---
Fix
Escape non-wildcard characters before compiling:
python def wildcardToRegexp(wildcard, delim=None): # Split on the two IMAP wildcards, escape everything else parts = re.split(r'([%])', wildcard) result = [] for p in parts: if p == '': result.append('(?:.?)') elif p == '%': if delim is None: result.append('(?:.?)') else: result.append('(?:(?:[^%s])?)' % re.escape(delim)) else: result.append(re.escape(p)) # ← escape all other characters return re.compile(''.join(result), re.I)
Alternatively, apply re.escape() to the entire wildcard first, then substitute the (now-escaped) \ and \% tokens back with their regex equivalents.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Compensating control
In Twisted's twisted/mail/imap4.py wildcardToRegexp(), escape all non-wildcard characters before compiling the regular expression; preserve only the two IMAP wildcard substitutions (* and %) as regex equivalents. Alternatively, apply re.escape() to the entire wildcard first, then substitute the escaped \* and \% tokens back with their regex equivalents.
Event History
Frequently Asked Questions
What access does an attacker need to trigger the issue?
The attacker must be an authenticated IMAP client able to send a quoted LIST or LSUB wildcard pattern. No user interaction is required.
What is the operational impact of a successful exploit?
A crafted pattern containing catastrophic-backtracking regular expression syntax can block a regex match. Because the Twisted reactor is cooperative and single-threaded, this freezes all server I/O for the duration of the match, causing a denial of service.