缓存投毒是怎么让\"正常页面\"变成攻击入口的?"

admin 2026-10-15 14:54:17 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

缓存投毒是怎么让\"正常页面\"变成攻击入口的?

原创

小新
小新

小新的安全运营笔记

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,可以记住一个简单公式:

未参与缓存键的输入 + 可控的危险响应 + 响应成功进入共享缓存 = 缓存投毒风险

而它最值得警惕的地方,就是攻击者污染的可能不是一个\"恶意链接\",而是一个大家每天都会访问的正常页面。

评论:0   参与:  0