缓存投毒
1. 缓存投毒 Web Cache Posioning#
1.1. 基础概念#
Web 缓存投毒攻击的核心在于: 攻击者找到一种方法, 让缓存系统存储并向其他用户提供恶意内容, 在 CDN 场景中, 这种攻击尤为危险:
- CDN 服务于大量用户
- 缓存内容可能持续较长时间
- 影响范围广泛且难以快速修复
这里说的缓存就是上面我们讨论的 CDN, 简单来说, 当一个用户访问了例如 /static/test.png
, Cache 服务将会缓存, 之后再有用户访问相同的资源时, Cache服务将不会转发此请求至后端服务器, 而是直接将其保存的对应的数据返回, 那这里有个题外话, CDN 判断两个请求是否相同的依据是什么? 答案是 缓存建, 缓存键通常由以下部分组成:
-
URL 路径
-
主机名
-
某些特定的请求头(如 Accept-Encoding)
-
查询参数(可配置是否包含)
用户请求 → CDN 边缘节点 → 检查缓存键 → 缓存命中返回内容/缓存未命中从源站获取
有一种简单的方式是基于 url 以及 host 头时, 那么缓存服务器将简单地认为下两个请求是等同的:
可以看到, 橙色字体将是缓存服务器判断的根据, 所以缓存服务器将会把第一个请求的响应拿来作为第二个请求的响应,
1.2. 简单例子#
举个例子: 利用未验证的 HTTP 头 假设有一个电商网站 shop.example.com
, 使用 CDN 加速内容分发, 该网站有一个功能, 根据用户的地理位置显示不同的促销信息, 网站后端代码:
// 服务器端代码
app.get('/promotions', (req, res) => {
// 从 X-Forwarded-Host 头获取主机名,用于构建资源 URL
const host = req.headers['x-forwarded-host'] || 'shop.example.com';
// 返回包含动态脚本引用的 HTML
res.send(`
<html>
<head>
<title>Today's Promotions</title>
<script src="https://${host}/scripts/promo.js"></script>
</head>
<body>
<h1>Special Offers</h1>
<!-- 页面内容 -->
</body>
</html>
`);
});
攻击者发现 X-Forwarded-Host
头被直接用于构建脚本 URL, 但这个头不是缓存键的一部分, 于是发送以下请求:
GET /promotions HTTP/1.1
Host: shop.example.com
X-Forwarded-Host: malicious-site.com
User-Agent: Mozilla/5.0 ...
-
服务器生成包含
<script src="https://malicious-site.com/scripts/promo.js"></script>
的响应 -
CDN 缓存这个响应,但缓存键中不包含
X-Forwarded-Host
头 -
所有后续访问
/promotions
的用户都会加载攻击者控制的 JavaScript
1.3. 实际案例 Akamai CDN 缓存投毒(2019)#
研究人员发现 Akamai CDN 配置中的一个问题, 允许攻击者通过操纵 HTTP 请求, 将恶意内容注入缓存:
POST /search HTTP/1.1
Host: vulnerable-site.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 128
Content-Length: 4
q=testGET /search?q=<script>alert('XSS')</script> HTTP/1.1
X-Ignore: X
两个 Content-Length 头
-
第一个声明内容长度为 128 字节
-
第二个声明内容长度为 4 字节
CDN 的处理:
-
Akamai CDN 可能使用第二个 Content-Length: 4
-
因此 CDN 只将
q=test
作为请求体,认为请求到此结束
源站服务器的处理:
-
源站可能使用第一个 Content-Length: 128
-
将整个内容视为一个请求体
-
但在处理后,剩余的内容
GET /search?q=<script>alert('XSS')</script> HTTP/1.1...
被解释为第二个请求
缓存污染:
-
源站对第二个走私请求的响应(包含 XSS 脚本)可能被 CDN 缓存
-
CDN 使用 URL 路径
/search?q=<script>alert('XSS')</script>
作为缓存键 -
后续用户访问
/search
时可能会收到包含 XSS 的缓存响应