GHSA-8pqf-f4m5-798g

ghsaUSosv

Twisted: IMAP wildcardToRegexp() ReDoS

view advisory published 2026-10-07Licence: CC-BY-4.0

### 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.perf_counter() rx.match(victim) print(f"n={n}: {time.perf_counter() - 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.

01

describes (1)

Related CVEs.

IDseverity exploitable
CVE-2026-106454US——
Twisted is an event-based framework for internet applications, supporting Python 3.6+. In 25.5.0 and earlier, wildcardToRegexp() in twisted/mail/imap4.py translates the IMAP asteri…
02

references

External citations — patches, advisories, analysis.

scry

Advisories like this name the exposed products attackers go after on your perimeter. See what is exposed on your perimeter with a free passive scan.

Free scan