缓存投毒是怎么让\"正常页面\"变成攻击入口的?
原创
小新
小新
小新的安全运营笔记
2026年10月13日 08:00
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
你有没有想过:一个看起来完全正常的首页,攻击者甚至不需要修改服务器代码,也不需要让受害者点击一个特殊链接,只要提前\"喂\"给缓存一份恶意响应,后面访问这个正常 URL 的用户,就可能拿到被污染的页面?
这就是缓存投毒。
一、先搞懂:缓存到底在做什么?
网站访问量大时,如果每次请求都让后端重新生成页面,服务器压力会很大。因此,网站通常会在用户和后端之间放一层缓存(CDN、反向代理等)。第一次访问时,缓存向后端要页面;后面的用户如果请求\"相同资源\",缓存可以直接把之前保存的响应返回出去。
问题就出在\\\"相同\"这两个字\\。
缓存不会认真理解业务,它通常根据一组字段生成 Cache Key(缓存键),例如请求方法、Host、URL 路径、查询字符串等。没有参与 Cache Key 计算的输入,就是所谓的 Unkeyed Input(未参与缓存键的输入)。
如果后端会使用这些输入改变页面内容,而缓存又把它们当成无关信息,就可能出现:
攻击者请求生成了恶意页面
↓
缓存把恶意页面存下来
↓
普通用户访问正常 URL
↓
缓存直接把恶意页面发给普通用户
二、一个接地气的例子:攻击者\"污染\"首页
假设网站首页正常访问是:
GET / HTTP/1.1
Host: www.example.com
网站为了生成绝对 URL,错误地信任了一个类似 X-Forwarded-Host 的请求头。攻击者发送:
GET / HTTP/1.1
Host: www.example.com
X-Forwarded-Host: attacker.example
后端可能据此生成一个资源地址,例如:
<script src=\"https://attacker.example/app.js\"></script>
如果这个请求头没有参与缓存键,而这份响应又被缓存,那么缓存保存的就不再是\"正常首页\",而是\"带有攻击者控制资源地址的首页\"。
此后,其他用户访问同一个正常首页 URL,缓存可能直接返回这份被污染的响应。
PortSwigger 的公开实验也展示了类似场景:未参与缓存键的 X-Forwarded-Host 被应用用于生成 JavaScript 资源地址,从而可以形成缓存投毒。
三、为什么它比普通 XSS 更麻烦?
普通反射型 XSS 往往需要受害者访问攻击者构造的恶意 URL;而缓存投毒的危险之处在于,攻击者的恶意输入可能先被缓存,之后受害者访问的是一个\"看起来完全正常\"的 URL。
例如:
攻击者发送特殊请求 → 后端生成恶意响应 → 缓存保存
↓
普通用户访问正常首页 → 缓存命中 → 收到恶意响应
如果恶意响应包含 XSS、恶意跳转或攻击者控制的 JavaScript 资源,那么影响范围可能从一个请求扩大到共享该缓存的其他用户。
OWASP 也指出,共享 Web Cache 中的恶意响应可能持续影响使用该缓存的多个用户,直到缓存被清理或过期。
四、渗透测试时重点看什么?
不要看到\"页面有缓存\"就直接判漏洞。真正需要确认的是下面三个条件是否同时成立:
\\第一,找到未参与 Cache Key、但会影响响应的输入。\\常见关注点包括 X-Forwarded-Host、X-Host、某些 Cookie、特定查询参数等。
\\第二,这个输入确实能改变后端响应。\\例如改变页面中的 URL、跳转地址、脚本来源、HTML 内容等。
\\第三,被改变后的响应能够进入缓存,并在后续正常请求中命中。\\只有\"影响响应\"而不能被缓存,通常不能直接认定为 Web Cache Poisoning。
测试时可以使用 Burp Suite Repeater 观察响应,并通过加入唯一的 Cache Buster(缓存随机参数) 避免自己的测试响应污染真实用户。Param Miner 也常用于辅助发现可能被应用处理、但未纳入缓存键的请求头。
⚠️ PortSwigger 特别提醒:在真实生产环境测试缓存投毒时,如果没有做好隔离,测试请求本身就可能污染其他用户看到的缓存内容。
五、常见的攻击入口有哪些?
| 入口 | 可能发生什么 | 测试关注点 |
|---|---|---|
| 未参与缓存键的 Header | Header 改变页面内容 | X-Forwarded-Host 等 |
| 未参与缓存键的查询参数 | 参数被反射/影响页面 | 整段 Query 或特定参数 |
| Cookie 等请求输入 | 用户状态影响公共响应 | 缓存是否错误共享 |
| 缓存键规范化问题 | 不同请求被当成同一个请求 | 大小写、路径、参数解析差异 |
六、怎么修?核心就一句:缓存和业务逻辑必须\"说同一种语言\"
最重要的是不要让后端使用一个输入改变响应,而缓存却完全不知道这个输入的存在。常见处理方式包括:
- 不要信任用户可控的 Host、
X-Forwarded-Host等请求头来生成关键 URL;如果确实需要使用,应由可信代理统一设置并校验。 - 如果某个请求字段会影响响应,就要评估是否应该纳入 Cache Key,或者干脆禁止该类响应被共享缓存。
- 涉及登录态、个性化数据、权限信息的页面,应谨慎使用公共缓存,避免把一个用户的响应共享给其他用户。
- 对会进入 HTML、脚本地址、跳转地址等位置的输入做好严格校验和输出编码。
- 发生污染后要及时清理 CDN/反向代理缓存,并检查是否已经存在被污染的页面或资源。
七、给开发/测试的 Checklist
| 检查项 | 开发/配置建议 | 测试重点 |
|---|---|---|
| Cache Key 是否合理 | 参与响应生成的关键输入不要被缓存忽略 | 修改 Header/参数后比较响应 |
| 是否存在公共缓存 | 登录态、个性化页面谨慎缓存 | 观察 Cache-Control、Age、X-Cache 等 |
| Host 类 Header 是否可信 | 只接受可信代理传递的值并校验 | 测试 X-Forwarded-Host 等 |
| 响应是否可被污染 | 对动态内容做校验/编码 | 确认恶意响应能否进入缓存 |
| 污染后影响范围 | 设置合理 TTL,提供主动清缓存能力 | 确认正常 URL 是否命中污染响应 |
八、最后总结
缓存投毒本质上不是\"缓存有漏洞\",而是应用和缓存对请求的理解不一致:后端认为某个输入很重要,会用它生成不同响应;缓存却认为这个输入不重要,把不同请求当成了同一个请求。
所以判断一个系统有没有 Web Cache Poisoning,可以记住一个简单公式:
未参与缓存键的输入 + 可控的危险响应 + 响应成功进入共享缓存 = 缓存投毒风险
而它最值得警惕的地方,就是攻击者污染的可能不是一个\"恶意链接\",而是一个大家每天都会访问的正常页面。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论