关于 hunt-cache-poison
hunt-cache-poison 是面向安全研究人员的 Web 缓存投毒测试技能,提供从识别缓存行为到验证影响范围的流程,并强调跨客户端复现与影响记录。
主要功能
- 01识别缓存设施与响应信号
- 02查找未纳入缓存键的标头和参数
- 03测试 URL 路径缓存欺骗
- 04验证投毒是否影响其他客户端
- 05评估缓存时长与影响范围
适用场景
- 01排查 CDN 缓存配置问题
- 02检查动态页面是否被当作静态资源缓存
- 03确认投毒响应是否会发送给其他用户
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-cache-poison安装后在 Claude Code 中输入/hunt-cache-poison确认已加载。
详细介绍
核心目标
缓存投毒具有很高的价值,因为一个被投毒的缓存条目就可能同时影响成千上万甚至数百万受害者——一次请求,即可大规模利用。赏金金额随影响范围扩大而增加。
价值最高的目标:
- 由 CDN 提供服务的资源(cdn.shopify.com、cloudfront 分发、Fastly/Akamai 边缘节点)——投毒后会影响全球所有访客
- 具有联盟营销/推荐流程的电商平台(Shopify、WooCommerce 店面)——可能导致会话劫持或联盟营销欺诈
- 带有更新服务器的游戏平台(rockstargames updates.* 域名)——对更新分发实施 DoS 会导致大量客户端无法正常使用
- 通过缓存提供服务的身份验证端点——可能导致账户接管(严重程度最高的变体)
- 资源 CDN(JS/CSS 分发)——大规模投放 XSS 有效载荷
- SaaS 多租户平台——共享缓存键的所有租户都会受到同一个被投毒响应的影响
收益最高的资源类型: CDN 主机名、按租户划分子域名的模式、更新/下载服务器、缓存配置错误的登录/账户页面、联盟链接短链接服务。
攻击面信号
需要留意的 URL 模式:
cdn.、assets.、static.、updates.、downloads.子域名- 带有类似静态文件扩展名的 URL 路径结构:
/path/to/page.css、/account.php/nonexistent.jpg - 联盟链接/短链接端点:
/link/、/go/、/ref/、/out/ - 将动态内容与看似可缓存的 URL 混合使用的路径
表明存在缓存的响应头:
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 技术栈
- 使用基于路径的路由、但不规范化尾随路径段的平台
- 会将未经验证的标头(Host、X-Forwarded-Host、X-Original-URL)反映到响应中的服务器
危险标头候选项(未纳入缓存键的输入):
X-Forwarded-Host
X-Host
X-Forwarded-Scheme
X-Original-URL
X-Rewrite-URL
Forwarded
X-HTTP-Method-Override
分步式狩猎方法
-
梳理缓存基础设施。 向目标发送 GET 请求并检查响应头。识别缓存层(Cloudflare、Fastly、Varnish、Nginx)。留意
Age、X-Cache、CF-Cache-Status响应头。 -
识别缓存键的组成部分。 发送两次相同的请求——如果
Age增加,则说明响应已缓存。逐一更改请求头(例如添加X-Forwarded-Host),确定哪些请求头未包含在缓存键中(未参与缓存键计算)。 -
测试未参与缓存键计算的请求头是否会被反射。 添加
X-Forwarded-Host: evil.com,检查该值是否出现在响应正文中(重定向、规范链接、CSP 响应头、JS src 属性、meta 标签)。追加一个唯一的缓存破坏查询参数(例如?cb=<random>),让探测请求以临时键命中缓存 MISS——这样可以验证反射情况,而不会过早地使用真实的、与受害者共享的缓存键存储有效的投毒条目。(Param Miner 的“猜测请求头”模式是用于自动发现这些未参与缓存键计算的请求头/参数的标准 Burp 工具链。) -
测试 URL 路径操纵(Web 缓存欺骗)。 在动态端点后追加伪造的静态扩展名:
GET /account/profile.cssGET /dashboard/settings.jpgGET /affiliate-link/target.js检查服务器是否返回动态内容,以及缓存是否存储了该内容。
-
测试是否能通过缓存投毒造成 DoS。 发送包含会导致 4xx/5xx 错误的请求头的请求,并检查该错误响应是否会被缓存:
- 格式错误的
Host请求头 - 指向无效主机的
X-Forwarded-Host - 触发后端错误的超大请求头
- 格式错误的
-
确认未参与缓存键计算的参数投毒。 尝试查询参数疲劳或 HTTP 参数污染:
GET /page?utm_source="><script>alert(1)</script>检查该参数是否会被反射,并缓存下来供对/page的正常请求使用。
-
验证缓存存储。 发送可能导致投毒的请求后,立即从不同 IP 或无痕会话请求相同的 URL,且不带恶意请求头。如果收到被投毒的响应,就确认攻击成功。
-
测量缓存 TTL。 检查
Cache-Control: max-age和Age,了解投毒会持续多久,以及在过期前是否可利用。 -
专门检查联盟/链接流程。 对于 Linkpop 等平台,测试引荐来源/产品 URL 是否嵌入可缓存响应,并会被其他用户收到。
-
记录影响范围。 确定缓存属于全球 CDN 边缘节点(覆盖全球)、区域缓存还是单服务器缓存。这会直接影响严重性评级。
载荷与检测模式
确认缓存行为:
# Send twice, compare Age header
curl --max-time 30 --connect-timeout 10 -s -I "https://target.com/page" | grep -i "age\|x-cache\|cf-cache"
curl --max-time 30 --connect-timeout 10 -s -I "https://target.com/page" | grep -i "age\|x-cache\|cf-cache"
测试未参与缓存键计算的 X-Forwarded-Host:
curl --max-time 30 --connect-timeout 10 -s -H "X-Forwarded-Host: evil.attacker.com" \
"https://target.com/page" | grep -i "evil.attacker.com"
测试 Web 缓存欺骗(追加路径):
# Authenticated session cookie required
curl --max-time 30 --connect-timeout 10 -s -b "session=YOUR_SESSION" \
"https://target.com/account/profile.css"
# Then fetch without auth from another client
curl --max-time 30 --connect-timeout 10 -s "https://target.com/account/profile.css"
强制缓存未命中,以在不访问缓存版本的情况下测试投毒:
# 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
# skip-storage, and Cloudflare/Fastly/Akamai generally ignore it on cacheable assets).
curl --max-time 30 --connect-timeout 10 -s -H "X-Forwarded-Host: canary.attacker.com" \
"https://target.com/page?cb=$RANDOM"
通过被投毒的错误响应造成 DoS:
curl --max-time 30 --connect-timeout 10 -s -H "X-Forwarded-Host: aaaaaaaaaaa.invalid" \
"https://target.com/js/app.js" -I
# Check if next clean request returns error
curl --max-time 30 --connect-timeout 10 -s -I "https://target.com/js/app.js" | grep "HTTP/"
在 Burp/ZAP 响应历史中搜索模式:
# Headers indicating cache hit
X-Cache: HIT
CF-Cache-Status: HIT
Age: [1-9]
# Reflected unkeyed input in body
evil\.attacker\.com
canary\d+\.
# Web cache deception indicators
Content-Type: text/css (but response is HTML/JSON)
Cache-Control: public.*max-age (on authenticated endpoint)
参数污染测试:
curl --max-time 30 --connect-timeout 10 -s "https://target.com/page?cb=1¶m=CANARY_VALUE" | grep CANARY_VALUE
# Then check if clean request returns poisoned version
curl --max-time 30 --connect-timeout 10 -s "https://target.com/page?cb=1"
用于未参与缓存键计算的请求头的 Burp Suite Intruder 字典:
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
常见根本原因
-
CDN 配置错误——仅根据 URL 路径缓存。 工程师配置诸如“缓存所有匹配
*.js的内容”之类的规则,却没有意识到动态路由后面可以附加路径。源服务器会忽略多余的路径段,但 CDN 会将它们用作缓存键。 -
转发标头未纳入缓存键。 开发者配置反向代理,将
X-Forwarded-Host转发给后端,用于生成 URL(规范链接、重定向、密码重置邮件),却没有将其纳入缓存键。CDN 缓存了被投毒的响应。 -
通过宽松路由实施 Web 缓存欺骗。 会规范化 URL 的框架(例如 Rails、Express)会接受
/account/settings.css,并返回与/account/settings相同的响应。CDN 看到.css扩展名后,会应用激进的缓存规则。 -
多租户响应被共享缓存。 使用单个 CDN 且缓存键中未隔离租户的 SaaS 平台会导致跨租户缓存投毒。
-
未加审慎处理就缓存错误响应。 由攻击者控制的输入触发的后端错误(404、500)会被缓存,导致合法用户遭受 DoS。开发者实施缓存时没有排除错误状态码。
-
Vary标头实现草率。 开发者知道应该添加Vary: X-Forwarded-Host,但常常忘记;或者 CDN 会完全移除/忽略Vary标头(Cloudflare 过去会移除某些资产类型的 Vary 标头)。 -
会反射 URL 的第三方集成。 联盟营销/链接跟踪系统(例如 Shopify Linkpop)会在元数据、规范标签或重定向中反射目标 URL,而这些内容会被全局缓存。
绕过技术
防御:WAF 阻止已知的投毒标头
- 绕过:使用不常见的标头变体:
X-Host、X-Forwarded-Server、X-HTTP-Host-Override、Forwarded: host=evil.com、X-Original-URL - 绕过:对标头值进行编码:
X-Forwarded-Host: evil%2ecom - 绕过:改变大小写:
x-forwarded-host、X-FORWARDED-HOST
防御:在边缘节点剥除攻击者提供的标头
- 绕过:如果代理会降级到 HTTP/1.1,则操纵 HTTP/2 伪标头
- 绕过:通过 HTTP 请求走私进行注入——将带有投毒标头的请求走私通过 WAF,直接访问缓存服务器
防御:缓存前要求进行身份验证
- 绕过:Web 缓存欺骗——在 URL 后附加
.css/.js,诱使缓存存储经过身份验证的内容,从而匹配会忽略身份验证的缓存规则
防御:缓存键包含完整 URL 和查询字符串
- 绕过:HTTP 参数污染——某些解析器会采用第一个出现的参数,而缓存则以完整字符串作为键;注入
?legit=1¶m=evil,缓存会将其存储在?legit=1¶m=evil下,但受害者访问的是?legit=1 - 绕过:超大 GET 请求——发送后端会处理、但缓存会忽略的正文参数
防御:缩短 TTL / 快速清除缓存
- 绕过:自动重新投毒;在 TTL 即将过期前循环发送投毒请求
- 绕过:通过特定 PoP 路由请求,针对默认 TTL 较长的 CDN 节点
防御:在敏感端点设置 Cache-Control: private
- 绕过:检查 CDN 是否遵循此标头(如果管理员已全局覆盖缓存规则,某些 CDN 会忽略此标头)
- 绕过:寻找会反射相同敏感数据的相邻可缓存端点
Gate 0 验证
-
攻击者现在能做什么? 攻击者必须能够污染缓存条目,并证明来自不同客户端/IP 的独立、未认证请求会收到被污染的响应,而不只是攻击者自己的浏览器收到响应。如果只有攻击者能看到效果,那就不算缓存投毒。
-
受害者会失去什么? 必须属于以下情况之一:(a) 通过被污染响应中反射的凭据导致会话/账户被攻陷;(b) 通过被污染的资源执行攻击者控制的 JS;(c) 服务拒绝,即合法请求返回错误响应;或 (d) 敏感数据泄露(账户详情被缓存并提供给其他用户)。仅有“奇怪的响应标头”不算影响。
-
能否在 10 分钟内从头复现? 你必须能够:发送投毒请求 → 等待缓存存储 → 从无痕窗口/不同 IP 获取该 URL → 观察被污染的响应。如果无法用第二个客户端演示这一干净的复现过程,缓存可能并未真正存储投毒内容,报告也还没准备好。
实际影响案例
场景 1 — CDN 资源投放遭大规模 DoS(Shopify CDN)
攻击者发现,cdn.shopify.com 上由 CDN 提供的 JavaScript 资源可以被投毒:只需发送带有特制标头的请求,导致源站返回 4xx 错误。CDN 缓存了这条错误响应。此后,任何加载该资源的商家店面都会收到缓存的错误响应,而非有效的 JS 文件,导致所有共用该 CDN 路径的店面结账流程和功能出现故障。只需一个 HTTP 请求,就会对所有商家造成影响,且影响会持续到缓存 TTL 过期。
场景 2 — 通过 Web 缓存欺骗接管账户
在一个提供已认证账户页面的平台上,攻击者构造了类似 /account/profile/photo.jpg 的 URL,并通过钓鱼链接发送给受害者。当已认证的受害者访问该 URL 时,服务器返回其完整账户资料页面(姓名、电子邮件、会话令牌)。由于 URL 以 .jpg 结尾,CDN 将已认证的响应公开缓存。随后,攻击者无需认证即可获取 /account/profile/photo.jpg,并收到受害者的账户数据,从而能够完全接管账户。由于缓存会向之后的任何请求者提供相同响应,影响进一步扩大。
场景 3 — 通过 URL 路径操纵劫持联盟链接(Shopify Linkpop) 攻击者发现,Linkpop 联盟链接服务会根据 URL 路径缓存响应,却会在缓存的 HTML 中反射经过操纵的产品目标 URL。攻击者在合法用户访问之前打开特制路径,污染缓存,将联盟链接点击重定向到攻击者控制的域名,而不是合法的 Amazon 商品页面。受害者点击看似有效的商家链接后会被引导至攻击者的基础设施,从而使攻击者得以进行凭据钓鱼,并导致合法商家损失联盟佣金收入。
已披露报告引用(2017-2024)
以下是真实且经过验证的漏洞赏金/协调披露案例,可扩展本技能的内容。涵盖两大类:缓存投毒(攻击者影响提供给受害者的缓存响应)和缓存欺骗(攻击者诱使缓存存储受害者的私有响应)。
-
Shopify — 通过 X-Forwarded-Host 进行缓存投毒(H1 #977851)
- 子类:X-Forwarded-Host 标头投毒(未纳入缓存键的输入 → 重定向/脚本来源损坏)
- 载荷:使用
X-Forwarded-Host: attacker.com发送GET /any-path请求 — 单个请求便将攻击者的主机持久写入缓存响应,影响apps.shopify.com及其本地化子域名 - 根本原因:X-Forwarded-Host 会影响资源/重定向 URL 的生成,但并未纳入缓存键
- 年份:2020 — $1,300 → 升级至 $6,300(一次投毒,影响波及多个主机)
-
HackerOne — 通过 X-Forwarded-Port 缓存投毒造成 DoS(H1 #409370)
- 子类:X-Forwarded-Host / X-Forwarded-Port DoS(将重定向投毒到无效端口)
- 载荷:使用
X-Forwarded-Port: 1发送GET /<redirect-path>——缓存的 301 重定向将合法用户指向端口 1,导致无法访问 - 根本原因:生成 301 重定向时信任 X-Forwarded-* 标头;缓存存储了错误的 Location
- 年份:2018 — $2,500(H1 针对 H1 的开创性案例)
-
GitLab — 通过 X-HTTP-Method-Override 缓存投毒造成 DoS(H1 #1160407)
- 子类:方法伪装 / GCS 缓存键泄漏(HEAD 响应存储在 GET 键下)
- 载荷:使用
X-HTTP-Method-Override: HEAD发送GET /assets/webpack/*.js——GCS 后端遵循了该方法覆盖标头并返回空响应体;CDN 将其缓存为标准 GET 响应 - 根本原因:CDN 缓存未区分请求方法;HEAD 响应体(空)覆盖了缓存静态资源的 GET 条目
- 年份:2021 — $2,500(DoS 通常不在范围内,此次因其新颖性而获奖励)
-
PayPal — Web 缓存欺骗(Omer Gil 原创)(博客)
- 子类:在需要身份验证的路由后追加
.css/.jpg等路径的经典 WCD - 载荷:
GET https://www.paypal.com/myaccount/home/foo.css——源站提供完整的已认证账户页面;CDN 将其作为“静态 .css”缓存约 5 小时 - 根本原因:源站将未知路径后缀路由到上级动态处理程序;CDN 仅根据看似静态的文件扩展名进行缓存
- 年份:2017 — $3,000(PortSwigger 2017 年十大 Web 黑客技术第 2 名)
- 子类:在需要身份验证的路由后追加
-
Cloudflare PBB — 通过
.avif绕过缓存欺骗防护(H1 #1391635)- 子类:利用冷僻的图片扩展名绕过 CDN 特定的允许列表(Cloudflare 的 WCD 防护功能)
- 载荷:
GET https://<protected-origin>/account/me.avif——Cloudflare 的 Cache Deception Armor 扩展名列表未包含.avif,因此已认证的 HTML 响应被缓存 - 根本原因:Cache Deception Armor 使用了静态且不完整的扩展名允许列表,未涵盖现代图片 MIME 类型
- 年份:2022 — Cloudflare PBB 奖金(金额未披露)
-
Akamai(PayPal/Airbnb/Goldman Sachs)— 逐跳标头走私 → 服务端边缘缓存投毒(Tediosi 和 Mariani 的分析文章)
- 子类:CDN 特定的请求走私,将攻击者的响应存入附近 IP 使用的 Akamai 边缘缓存
- 载荷:
Connection: Content-Length+ 精心构造的请求 ——Akamai 的第一个代理将 Content-Length 作为逐跳标头剥离;第二个代理则将请求体视为第二个请求,其响应被缓存在边缘 - 根本原因:Akamai 各代理层对逐跳标头的处理不一致,导致请求失步;走私的响应被全局缓存在服务端
- 年份:2022 — 受影响项目的总额 >$50K(PayPal $25,200 + Airbnb $14,875 + Goldman Sachs $100),入围 PortSwigger 2022 年十大 Web 黑客技术
-
James Kettle(PortSwigger)——“Gotta cache 'em all”:针对 Cloudflare/Fastly/GCP 的路径规范化与 WCD(PortSwigger Research)
- 子类:主要 CDN(Cloudflare、Fastly、GCP/Google Cloud)中的缓存键路径规范化差异和 Web 缓存欺骗——这项 2024 年的研究在本技能的描述中有所提及
- 载荷:源站与缓存对分隔符/规范化的处理不一致(例如,编码后的路径段和静态后缀技巧),导致缓存将动态/经过身份验证的响应存储在看似静态的键下
- 根本原因:缓存与源站对 URL 路径的规范化/解析方式不一致,因此缓存键与源站实际提供的资源不匹配
- 年份:2024 年——与 CDN 供应商协调披露;方法论研究(无单项漏洞赏金),入选 PortSwigger 2024 年十大 Web 黑客技术
验证
运行此自测,确认缓存投毒探测准备就绪:
-
缓存标头检测——确认缓存标头识别功能:
echo "X-Cache: hit" | grep -q "X-Cache" && echo "PASS: cache header recognized" || echo "FAIL" echo "CF-Cache-Status: HIT" | grep -q "CF-Cache" && echo "PASS: Cloudflare cache recognized" || echo "FAIL" -
未纳入缓存键的标头注入——验证标头探测语法:
echo "X-Forwarded-Host: evil.com" | grep -q "X-Forwarded-Host" && echo "PASS: unkeyed header syntax" || echo "FAIL" -
带请求体的 GET 测试——确认支持带请求体的 GET 请求:
curl --max-time 30 --connect-timeout 10 -s -X GET "https://httpbin.org/get" -d "test=body" 2>/dev/null | grep -q "test" && echo "PASS: GET with body supported" || echo "(httpbin may be down)"
全部 3 项测试均可验证缓存投毒探测准备就绪。
注意事项
- 反射未纳入缓存键的标头,但未命中缓存——标头必须被反射,且响应必须已缓存(X-Cache: hit)。仅有反射而未命中缓存,不属于缓存投毒。
- 单次探测产生的误报——一次返回 200 且 X-Cache: hit 并不能证明发生了投毒。需要确认来自不同 IP 的多次请求均持续命中缓存。
- 忽略 Vary 标头——如果设置了
Vary: User-Agent,你的投毒只会影响使用相同 UA 字符串的用户。请记录其影响范围。 - 带请求体的 GET 投毒——一些 CDN 仅根据 URL 缓存,忽略请求体。带有精心构造请求体的 GET 请求可能污染该 URL 对所有用户的缓存。
- 无共享缓存时的 Host 标头投毒——如果缓存按源站隔离(而非共享),投毒只会影响你自己的请求。请从不同 IP/浏览器进行测试。
- Web 缓存欺骗与投毒——欺骗会诱使缓存将敏感数据存储为静态内容;投毒则会注入恶意内容。不要混淆二者。
相关技能与链路
hunt-xss— 缓存投毒是放大器,可将反射型 XSS(危害较低、仅影响自身)转变为影响 CDN 边缘节点上每位访客的存储型 XSS。攻击链原语:X-Forwarded-Host: attacker.com污染缓存的 script src → 缓存响应现在包含<script src="//attacker.com/x.js">→ 该 CDN 边缘节点上的每位访客都会执行攻击者的 JS,且会在整个 Cache-Control max-age 期间持续生效。hunt-http-smuggling— 请求走私可绕过前端缓存键规范化和 WAF 对投毒标头的剥除,直接攻击缓存服务器。攻击链原语:CL.TE 走私将X-Forwarded-Host: attacker.com发送到缓存后端,绕过边缘节点上剥除该标头的 WAF → 投毒条目以受害者的常规 URL 存储 → 发生去同步投毒,走私请求成为下一位受害者收到的缓存响应。hunt-auth-bypass— Web 缓存欺骗会将已认证页面变成公开缓存的响应,向未认证攻击者泄露与会话绑定的内容。攻击链原语:/account/profile.css以已认证 HTML 的形式提供,并作为静态资源缓存 → 攻击者无需认证即可获取相同 URL 并读取受害者的电子邮件/令牌 → 响应正文中含有会话 Cookie → 完全接管账户(ATO)。security-arsenal— 在手动模糊测试前,先使用无键标头词表(X-Forwarded-Host、X-Host、X-Forwarded-Server、X-HTTP-Host-Override、Forwarded、X-Original-URL)和 WCD 路径扩展名列表(.css、.js、.jpg、.ico、;.css、%2e%2ecss)。triage-validation— 声称为严重级别 Critical 前,先运行 Pre-Severity Gate:必须能在不同 IP/无痕模式下,不带你的投毒标头复现被投毒的响应。如果只有你自己的浏览器能看到效果,那只是自身缓存,应判定为 N/A。