Web缓存欺骗攻击的本质,就是攻击者通过构造特殊的HTTP请求,让服务器或CDN缓存返回错误的内容——比如把其他用户的敏感页面缓存下来,或者把原本不该缓存的动态内容存进了缓存层。而缓存键规范化,就是从根源上解决"什么样的请求应该生成独立缓存"这个问题,通过统一、严格的缓存键生成规则,杜绝攻击者利用请求头、URL参数、Host头等字段的歧义来污染缓存池。这两个话题放在一起,核心逻辑就是:缓存键不规范,缓存欺骗就有可乘之机;缓存键规范了,攻击面大幅收窄。
一、Web缓存欺骗攻击到底是怎么回事
要理解缓存欺骗,先得明白Web缓存的工作原理。当用户访问一个网站时,请求可能先经过CDN节点或反向代理缓存服务器。如果缓存里已经有这个请求对应的响应内容,就直接返回,不再打到源站。缓存服务器判断"有没有对应内容"的依据,就是缓存键(Cache Key)。缓存键通常由URL路径、查询参数、部分请求头(如Accept-Language、Cookie)等拼接而成。
问题就出在这里。如果缓存键的生成规则不够严谨,攻击者就可以精心构造请求,让缓存服务器把一个"本不该被缓存"或者"属于别人的"响应存下来。常见的攻击手法有以下几种:
第一种叫"缓存键歧义攻击"。比如缓存服务器只用URL路径作为缓存键,忽略了Host头。攻击者发送一个请求,Host头填的是内部管理后台的域名,但路径是普通页面路径,缓存服务器就把管理后台的内容缓存到了普通页面的缓存键下。其他正常用户访问时,拿到的就是管理后台的页面。
第二种叫"参数污染攻击"。有些缓存系统会把所有查询参数都纳入缓存键,但处理方式不统一。比如有的系统会把参数排序后再生成键,有的不排序。攻击者利用这个差异,构造带有相同参数但不同顺序的请求,绕过缓存键匹配逻辑,让恶意响应被缓存。
第三种叫"请求头注入攻击"。攻击者在请求中加入X-Forwarded-Host、X-Original-URL等非标准头,部分缓存服务器会把这些头也纳入缓存键计算。攻击者利用这些头伪造请求来源,让缓存服务器存储错误的响应内容。
二、缓存键规范化的核心原则
缓存键规范化,说白了就是制定一套铁律,明确规定哪些字段参与缓存键计算、以什么顺序、什么格式参与。以下是几条核心原则:
1. 明确纳入范围
只有真正影响响应内容的字段才应该纳入缓存键。通常包括:请求方法(GET/POST)、URL路径(不含查询参数的排序变体)、必要的查询参数(如language、currency等影响内容的参数)、Accept-Encoding、Accept-Language。而Cookie、Authorization、Host(在多站点托管场景下必须纳入)等字段,要根据业务场景严格判断。
2. 统一处理顺序和格式
对于查询参数,必须先按字母顺序排序,再拼接成字符串。对于URL路径,必须做统一的解码和规范化处理(比如去掉多余的斜杠、统一大小写)。对于请求头的值,要做trim处理,去掉首尾空格,统一大小写。
3. 排除危险字段
像X-Forwarded-For、X-Forwarded-Host、X-Original-URL这类容易被攻击者伪造的头,绝对不能纳入缓存键计算。如果业务确实需要根据客户端IP做缓存区分,应该在边缘层用可信的连接信息,而不是依赖客户端可控的请求头。
4. 敏感路径禁止缓存
对于登录页、支付页、个人中心等敏感路径,直接设置Cache-Control: no-store或private,从策略层面杜绝缓存,而不是试图通过缓存键来区分。这是最简单也最有效的防线。
三、具体怎么做缓存键规范化——实操层面
下面以Nginx和Varnish两种常见的缓存系统为例,说明具体的配置方法。
Nginx环境下的缓存键规范化
Nginx默认的proxy_cache_key只包含请求方法和URL,这在很多场景下是不够的。你需要自定义缓存键:
proxy_cache_key "$scheme$request_method$host$uri$is_args$args";
这里$host确保了多站点场景下不同域名的缓存隔离,$is_args$args确保了带参数和不带参数的请求分开缓存。但这还不够,如果你的业务需要根据语言做缓存区分,还需要加入Accept-Language:
proxy_cache_key "$scheme$request_method$host$uri$is_args$args$http_accept_language";
更关键的一步是,在Nginx层面过滤掉危险的请求头。可以用map指令把X-Forwarded-Host等头的值清空,防止它们影响上游逻辑:
map $http_x_forwarded_host $clean_xff_host {
default "";
"~." $http_x_forwarded_host;
}
这样即使攻击者发送了X-Forwarded-Host,Nginx也不会把它当回事。
Varnish环境下的缓存键规范化
Varnish的缓存键在vcl_hash子程序中定义,默认只用URL和Host。更完善的写法是:
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
}
if (req.http.Accept-Language) {
hash_data(req.http.Accept-Language);
}
# 排除Cookie对缓存键的影响(除非业务需要)
if (req.http.Cookie) {
# 只取必要的cookie字段,如session_id之外的偏好设置
hash_data(req.http.Cookie);
}
return (hash);
}
同时在vcl_recv中要主动清理危险头:
sub vcl_recv {
# 移除客户端可控的伪造头
unset req.http.X-Forwarded-Host;
unset req.http.X-Original-URL;
unset req.http.X-Rewrite-URL;
# 敏感路径直接跳过缓存
if (req.url ~ "^/(login|checkout|account)") {
return (pass);
}
}
四、CDN层面的防护策略
如果你使用的是商业CDN服务,缓存键的配置通常在控制台里完成。大部分主流CDN都支持自定义缓存键规则,核心要点是:开启"忽略查询参数"功能时要谨慎,只对确实不影响内容的参数忽略;开启"缓存键包含请求头"时,只选Accept-Language、Accept-Encoding这种安全的头;绝对不要开启"根据X-Forwarded-Host缓存"这类选项。
另外,CDN层面还要配置缓存刷新策略。一旦发现缓存被污染,能够通过API快速批量清除特定缓存键的内容,把损失控制在最小范围。建议建立缓存监控告警机制,对缓存命中率异常波动、特定缓存键返回非预期状态码等情况实时告警。
五、纵深防御——缓存键规范化之外还要做什么
缓存键规范化是核心手段,但不是唯一手段。完整的防护体系还需要以下几层:
第一层:源站响应头加固。源站必须正确设置Cache-Control、Vary等响应头。Vary头告诉缓存服务器哪些请求头会影响响应内容,比如Vary: Accept-Language, Accept-Encoding,这样缓存服务器才知道要根据这些头做缓存区分。
第二层:输入验证和过滤。在应用层对所有用户输入做严格验证,包括URL参数、请求头值。对特殊字符、超长参数、编码变体等做拦截。这不仅防缓存欺骗,也防注入攻击。
第三层:定期审计缓存内容。通过自动化脚本定期抽样检查缓存内容是否正确,特别是对高风险路径(如用户相关页面)的缓存做重点审计。发现异常立即清除并追溯原因。
第四层:日志分析和攻击检测。记录所有缓存命中和未命中的日志,分析是否有异常的缓存键模式。比如短时间内大量不同Host头访问同一路径、大量带有X-Forwarded-Host的请求等,都是缓存欺骗攻击的信号。
六、常见误区和注意事项
很多团队在做缓存键规范化时容易犯几个错误。一是过度缓存,把所有内容都缓存起来,觉得性能优先。实际上敏感内容绝对不能缓存,性能和安全必须平衡。二是缓存键设计过于复杂,把太多字段纳入计算,导致缓存命中率极低,反而增加源站压力。三是只关注技术层面,忽略了运维层面的监控和应急响应。缓存被污染后如果不能快速发现和清除,影响会持续扩大。
还有一个容易被忽视的点:HTTP/2和HTTP/3环境下,伪头部(如:authority)的处理方式和HTTP/1.1不同,缓存键生成逻辑需要适配新协议。另外,随着边缘计算的普及,越来越多的逻辑在边缘节点执行,缓存键的生成可能分散在多个系统中,需要统一治理。
七、总结
Web缓存欺骗攻击是一种利用缓存机制本身缺陷的高危害攻击,它不需要突破源站的安全防线,只需要在缓存层制造混乱就能窃取敏感信息或篡改用户看到的内容。缓存键规范化是对抗这种攻击最直接、最有效的技术手段,核心就是:明确哪些字段参与、统一处理规则、排除危险字段、敏感路径禁缓。但技术手段只是基础,还需要配合响应头策略、输入过滤、监控审计、应急响应形成完整的防护闭环。做好这些,你的网站缓存层才能真正成为性能加速器,而不是安全短板。
