HTTP学习之路:代理中的缓存投毒

代理转发请求过程中有个很常用的头,叫做X-Forward-Host,会让攻击者恶意利用进行缓存投毒。

一.问题背景

在一个典型的Web架构中,用户访问公网域名 www.example.com,流量首先到达部署在公网的负载均衡器。负载均衡器负责将请求分发到内网多台应用服务器(如 192.168.1.20 )中的某一台,以实现高可用和扩展性。

核心矛盾点:

  1. 虚拟主机依赖: 一台内网应用服务器通常通过虚拟主机技术托管数十个网站,它完全依赖HTTP请求头中的 Host 字段来判断用户想访问哪个具体网站。

  2. 内外网寻址差异: 公网域名 www.example.com 在内部网络可能无法解析或解析到错误地址,导致负载均衡器的转发请求失败。

二、 核心挑战

如果负载均衡器将原始请求(Host: www.example.com)直接转发给内网服务器 192.168.1.20(app-server-1.internal),会导致两个问题:

  1. 路由错误: 请求发往 www.example.com 而非目标服务器的内网IP,可能在内网中路由失败。

  2. 网站识别错误: 应用服务器 192.168.1.20 收到 Host: www.example.com 的请求后,会试图寻找自身配置中名为 www.example.com 的站点。如果找不到,将返回错误或默认页面,而非用户想要的网站。

三、 解决方案

负载均衡器在转发请求时,执行以下两个关键操作:

  1. (1)修改目标地址与Host头(用于正确路由)

  2. 将请求的目标地址改为应用服务器的内网IP或内部域名(如 app-server-1.internal)。

  3. 目的:确保请求能在内网环境中被准确路由到目标机器,并被其接受。

  4. (2)添加X-Forwarded-Host头(用于保留原始信息)

  5. 在修改Host头之前,将原始的 Host 值(www.example.com)存入一个名为 X-Forwarded-Host 的自定义HTTP头中。

  6. 目的:无损地传递用户的原始访问意图,供后端应用服务器识别。

转发前后的请求头变化:

  • 用户 -> 负载均衡器的请求头:

  • 负载均衡器 -> 应用服务器 的请求头:

四、 后端应用服务器的配合

应用服务器(如Nginx、Apache)必须被配置为能够识别和处理 X-Forwarded-Host 头。

五、 安全风险

上述方案引入了一个关键的安全依赖:后端服务器必须无条件信任由负载均衡器设置的 X-Forwarded-Host 等头。如果配置不当,攻击者可以伪造这些头信息,进行缓存投毒攻击。


攻击过程如下:

第1步:攻击者发送恶意请求

攻击者直接向负载均衡器发送一个精心构造 HTTP请求。他手动添加了 X-Forwarded-Host 头,试图覆盖其值。

攻击者 -> 负载均衡器:

请注意: 攻击者完全可以在自己的客户端(如Burp Suite)中轻松地设置任何HTTP头。

第2步:负载均衡器处理并转发请求

负载均衡器收到此请求,按照既定规则进行处理:

  1. 它看到 Host: www.example.com,将其改写为 Host: 192.168.1.20

  2. 它看到请求中已经有一个 X-Forwarded-Host 头。负载均衡器的常见行为是:要么追加一个值,要么直接覆盖它。在很多默认配置下,它可能会信任并保留客户端传来的这个头,或者在其后追加自己的值(如 X-Forwarded-Host: evil.com, www.example.com),这取决于具体配置。最危险的情况就是它直接原样转发。

我们假设一个不安全的配置,负载均衡器简单地转发了客户端提供的头:

负载均衡器 -> 后端服务器 (192.168.1.20):

第3步:后端服务器生成恶意响应

后端服务器 192.168.1.20 收到请求。它的应用逻辑运行:

  1. 它发现了 HTTP_X_FORWARDED_HOST 存在,其值为 evil.com

  2. 它完全信任这个值,并用它来生成页面内容。

后端服务器 -> 负载均衡器: 返回包含了指向攻击者控制域名 evil.com 的脚本。

第4步:缓存服务器存储恶意响应(投毒)

负载均衡器/缓存服务器收到后端返回的这个响应。

  1. 生成缓存键 :它的缓存键规则是 Host + URL。它看的是最初收到的请求中的 Host 头,即 www.example.com,加上路径 /index.html。所以缓存键是www.example.com/index.html

  2. 决定缓存 :它看到响应头中有 Cache-Control: public, max-age=3600,于是决定缓存此响应。

  3. 执行存储 :它将这个恶意响应,存储到了 www.example.com/index.html 这个缓存键下。

第5步:无辜用户访问中毒的缓存

此后一小时内,任何普通用户访问 https://www.example.com/index.html

  1. 他们的请求 GET /index.html HTTP/1.1 Host: www.example.com 到达负载均衡器。

  2. 负载均衡器生成缓存键 www.example.com/index.html,立即命中第4步中存储的恶意缓存。

  3. 负载均衡器不再向后端请求,直接将该恶意响应返回给用户。

  4. 用户的浏览器解析HTML,向 evil.com 请求并执行那个恶意的 app.js 脚本。攻击者成功窃取用户cookie或其他敏感信息。

解决办法:

防御策略1:严格校验与过滤HTTP头

漏洞代码示例(javascript):

防御策略2:谨慎设置缓存响应头

对包含用户动态内容的页面,正确设置 Cache-Control头,避免其被公共缓存。

防御策略3:配置缓存键(Cache Key)

默认的缓存键可能只包含 req.urlreq.http.Host。我们需要修改它,将可能影响响应内容的头(如 X-Forwarded-Host)加入缓存键。这样配置后,一个带有 X-Forwarded-Host: evil.com 的请求和正常的请求,会因为缓存键不同而被区别对待,无法污染公共缓存。

相关推荐
2502_9116791415 小时前
精准与稳定的基石:Agilent 66311B,为移动通信测试量身定制的核心供电单元
大数据·网络·5g·信息与通信·信号处理
亚远景aspice15 小时前
亚远景-满足ASPICE要求的配置管理(SUP.8)与变更管理策略
大数据·网络·安全·汽车
深眸财经15 小时前
2026,直播电商“卷”向何处?
网络
进阶的小名16 小时前
[超轻量级延时队列(MQ)] Redis 不只是缓存:我用 Redis Stream 实现了一个延时MQ(自定义注解方式)
java·数据库·spring boot·redis·缓存·消息队列·个人开发
短剑重铸之日16 小时前
《7天学会Redis》Day 6 - 内存&性能调优
java·数据库·redis·缓存·7天学会redis
深眸财经16 小时前
繁荣与崩塌,2026国产美妆站上「十字路口」
大数据·网络
kkce16 小时前
域名CDN检测意义
服务器·前端·网络
(Charon)16 小时前
【DPDK实战】编写一个高性能 UDP 抓包程序
网络·网络协议·udp
fy zs16 小时前
网络层IP协议的初步认识
服务器·网络·tcp/ip
a努力。16 小时前
字节跳动Java面试被问:一致性哈希的虚拟节点和数据迁移
java·开发语言·分布式·算法·缓存·面试·哈希算法