CDN缓存中毒是一种针对内容分发网络共享缓存层的攻击方式,攻击者通过操纵某些被CDN忽略但被源站使用的请求参数,让源站返回错误甚至恶意的内容,并被CDN当作合法响应缓存起来。此后所有命中同一缓存键的用户都会收到被污染的数据,造成大范围的页面篡改、脚本注入或跳转劫持。理解这类问题的核心,在于搞清楚CDN的缓存键到底由哪些部分构成,以及源站为何会信任那些不在缓存键中的请求头。

CDN缓存键与源站处理的错位原理
大多数CDN默认只把请求的URL路径、查询字符串以及少数指定头作为缓存键,也就是说,两份请求只要这些关键部分相同,CDN就认为它们是同一个资源。但源站应用在生成响应时,往往会读取Host、X-Forwarded-Host、X-Original-URL等头部来拼接重定向地址或静态资源链接。这种缓存键与业务参数的错位,正是缓存中毒能够成立的技术根基。
举个简单的例子,源站代码里用请求头中的X-Forwarded-Host来构造登录页的跳转地址,而CDN并没有把这个头纳入缓存键。攻击者在第一次请求时带上一个恶意的X-Forwarded-Host值,源站返回包含恶意域名的响应,CDN因为缓存键没变就将其保存。接下来正常用户访问同样URL时,拿到的就是带恶意跳转的页面。下面这段Node.js示例展示了危险的写法:
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
// 错误示范:直接使用转发头拼装跳转地址
const host = req.headers['x-forwarded-host'] || req.hostname;
res.send('<a href="https://' + host + '/auth">请登录</a>');
});
app.listen(3000);
上面的代码里,一旦CDN不把x-forwarded-host当作缓存键,攻击请求和正常请求的缓存键一致,污染响应就会被复用。要解决这个错位,第一步是梳理出业务里所有基于请求头动态生成响应的位置,确认它们是否落在CDN的缓存键之外。只有把“影响响应的输入”和“决定缓存的输入”对齐,才能谈得上防护。
典型缓存中毒攻击路径与利用方式
最常见的利用路径是借助Host类头部进行开放重定向或前端资源替换。攻击者将X-Forwarded-Host设为恶意站点,源站返回的HTML中引用了https://恶意站点/xxx.js,CDN缓存后,普通用户加载页面时就会拉取攻击者控制的脚本,形成存储型XSS的放大版。由于CDN边缘节点遍布各地,一次污染可能影响数以万计的访客。
另一种路径是利用未纳入缓存键的查询参数或Cookie。某些源站会根据特定Cookie切换页面语言并内联样式,但CDN按URL缓存,导致A语言用户的页面被B语言用户看到,若攻击者注入带钓鱼表单的本地化内容,同样构成中毒。下面是一段PHP中容易被忽视的写法:
<?php
// 根据未参与缓存键的 cookie 输出不同内容
$lang = $_COOKIE['lang'] ?? 'cn';
if ($lang === 'en') {
echo '<script src="https://evil.com/p.js"></script>';
}
?>
如果CDN只按路径缓存,而Cookie不在键中,这段恶意脚本就可能被缓存并投送给所有访客。实践中,安全团队应当用自动化工具遍历所有请求头与参数,观察哪一些能改变响应体却不改变缓存键。这种差分测试是发现中毒面的有效手段,比单纯看文档更可靠。
还有一类利用方式是配合缓存碎片投毒,攻击者先污染某个被页面包含的片段接口,再让主页面由于引用了该片段而被连带缓存。这种组合攻击在微服务架构下尤其隐蔽,因为边缘缓存规则往往只配置了主域名,对内网接口缺乏约束。
防御CDN缓存中毒的落地措施
最根本的防御是收紧缓存键,把任何会影响源站输出的头与参数都加入CDN的缓存键配置,或者直接在边缘节点丢弃、覆盖不可信的头。例如Cloudflare和主流CDN都支持自定义缓存键,将X-Forwarded-Host排除在信任范围之外,或固定为一个内部域名。这样攻击者即便发送异常头,也不会改变缓存归属。
源站侧也必须消除对外部头的隐式信任。前面Node.js示例应改为从服务端配置读取合法域名,而不是读请求头。下面是修正后的写法:
const express = require('express');
const app = express();
const ALLOWED_HOST = 'www.ipipp.com';
app.get('/login', (req, res) => {
// 正确示范:使用固定配置而非请求头
res.send('<a href="https://' + ALLOWED_HOST + '/auth">请登录</a>');
});
app.listen(3000);
此外,应对响应内容做完整性校验,尤其是脚本与样式链接的域名白名单。CDN层面可开启“缓存清除告警”,当同一资源响应体发生明显变化时触发人工审核。对于涉及用户相关内容的接口,应明确标记Cache-Control: private,避免被公共边缘节点缓存。综合来说,缓存中毒的治理不是单点补丁,而是缓存键设计、源站输入校验与监控响应三者协同的工程问题。