HTTP请求走私(HTTP Request Smuggling)听起来像一个生僻的安全名词,但它背后的攻击逻辑其实很朴素:当链路上存在多台服务器(比如前面是Apache反向代理,后面是Tomcat应用服务器)时,如果两台服务器对同一个HTTP请求结束位置的判断不一致,攻击者就可以构造一个精心设计的请求,让前置服务器认为它是一个请求,而后端服务器把它拆成两个请求。这样一来,第二个“隐藏请求”就 smuggle(走私)过了前置服务器的安全检查,直接抵达后端。Apache HTTP Server 作为全球部署量最大的Web服务器之一,历史上多次出现与请求走私相关的CVE漏洞,理解其原理并做好防护非常必要。

一、请求走私的底层原理与常见类型
要理解请求走私,首先要明白HTTP/1.1协议中两个决定请求边界的头部:Content-Length(简称CL)和Transfer-Encoding(简称TE)。前者用字节数标明请求体的长度,后者声明使用分块编码,请求体由若干块组成,以一个零长度块结尾。协议规定当两者同时出现时,TE优先级更高。问题在于,解析器的实现细节千差万别,有的严格遵循规范,有的做了容错处理,有的干脆在异常时行为未定义。
当前后两台服务器对这些边界场景的处理不一致时,差异就被攻击者利用。常见的走私类型有三种。第一种是CL.TE:前置服务器按Content-Length截断,后端按Transfer-Encoding解析,多出来的数据被留在了连接缓冲区里,成为下一个请求的开头。第二种是TE.CL:方向相反,后端按Content-Length解析时,会把攻击者预埋的第二个请求当作普通数据处理。第三种是TE.TE:双方都看到Transfer-Encoding,但由于头部变形(比如大小写混写、添加垃圾字符、重复头部),其中一端无法识别而退回按CL处理,同样产生解析分歧。
举个典型的攻击载荷示意:
POST / HTTP/1.1 Host: www.ipipp.com Content-Length: 6 Transfer-Encoding: chunked 0 G
上面这个请求,如果前置的Apache按CL取6个字节,它会把整个报文当作一个完整请求转发;而后端按chunked解析,读到0块就认为请求结束,末尾多出来的字符G会被拼接到同一连接上的下一个请求开头,破坏甚至替换掉受害者的正常请求。这就是最基础的分歧构造方式。
二、Apache相关的典型漏洞案例分析
Apache HTTP Server 近年披露过多个与请求解析相关的漏洞。比较有代表性的是CVE-2021-33018和更早的CVE-2005-2088等。以较新的问题为例,当Apache作为反向代理,且上游配置了某种特定的请求体处理方式时,攻击者通过构造Content-Length与Transfer-Encoding同时存在或畸形变形的请求,可以让代理转发的内容与后端解析的内容错位,从而实现请求走私,进而绕过访问控制、劫持会话,或在缓存服务器上投毒。
另一个值得关注的场景是 mod_proxy 与后端配合使用 chunked 转发时的边界处理问题。部分版本在遇到非标准的 Transfer-Encoding 值时没有直接拒绝,而是尝试继续解析,这违反了“遇到无法理解的TE头部应当返回400错误”的最佳实践。此外,如果Apache前面还有CDN或负载均衡设备,形成三级链路,中间任何一环的解析差异都可能被串联利用,排查难度会显著增加。
判断环境是否存在风险,最直接的办法是做版本核对:使用httpd -v查看当前版本,再对照Apache官方安全公告确认是否在受影响范围内。也可以借助开源工具(如smuggler、htb-smuggler类的检测脚本)对测试环境发送无害的边界探测请求,观察后端日志是否出现异常拆分。切记不要直接对生产环境或他人的站点做这类测试,这可能违反法律法规。
三、多层防护方案与配置加固
第一层也是最有效的手段是及时升级。Apache官方对每一个边界解析类漏洞都会发布修复版本,升级到当前稳定分支的最新版基本可以消除已知问题。如果暂时无法升级,可以通过配置降低风险。在Apache 2.4.x中,可以借助mod_headers对可疑请求做拦截,例如检测到请求同时携带CL和TE时直接拒绝:
# 拒绝同时携带 Content-Length 和 Transfer-Encoding 的请求
<If "%{HTTP_CONTENT_LENGTH} != '' && %{HTTP_TRANSFER_ENCODING} != ''">
Header set Connection close
RequestHeader unset Transfer-Encoding
</If>
# 更严格的做法:配合 mod_security 拦截变形TE头
SecRule &REQUEST_HEADERS:Transfer-Encoding "@gt 0" "id:1001,phase:1,deny,status:400,msg:'TE header present'"第二层是架构层面的规范。尽量避免让请求链路上的多台设备对HTTP报文做二次解析,能直接透传就透传。如果必须使用反向代理,尽量让前后端使用相同的HTTP解析实现,或者干脆升级到HTTP/2——HTTP/2采用二进制帧结构,报文边界由帧决定,天然不存在CL与TE的歧义问题。同时建议关闭后端连接复用(Keep-Alive)或缩短复用时长,这虽然牺牲一点性能,但能破坏走私攻击赖以生存的“连接内拼接”条件。
第三层是监控与审计。请求走私攻击发生时,后端日志常会出现一些典型痕迹:同一个来源IP发出语义上自相矛盾的请求、出现来源不明的405或404、日志中记录的请求比前置代理记录的数量多。部署日志比对机制,定期统计代理层与应用层的请求数差值,一旦发现明显偏差就要警惕。对于使用了缓存的环境,还应当对缓存键做校验,避免攻击者通过走私投毒缓存影响其他用户。
四、开发与运维侧的补充建议
应用开发侧也要有防护意识。自定义的HTTP客户端如果自己拼请求报文,务必保证不要同时输出CL和TE两个头部;解析请求的服务端代码遇到畸形头部时应果断返回400而不是尝试容错。历史经验表明,大量走私漏洞的根因正是“过度宽容的解析”。RFC 7230明确要求,服务器收到同时包含两个头部的请求应当报错或关闭连接,实现上照做即可规避一大类问题。
运维侧则要建立漏洞跟踪流程。订阅Apache官方的安全通告邮件列表,配置自动化补丁扫描,对mod_proxy、mod_cache这类承担报文转发职责的模块重点关照。在容器化环境中,注意基础镜像里的Apache版本可能长期未更新,构建流水线里加入版本基线检查能有效堵住这个盲区。最后,定期开展渗透测试,把请求走私纳入测试用例库,验证防御措施是否真正生效,而不是停留在配置文件里的一行注释上。安全从来不是一次性动作,持续的版本管理加上严格的解析规范,才能让这类协议层攻击无机可乘。