HTTP 请求走私:两个服务器之间的猫鼠游戏

你和朋友之间的对话,如果有人从中偷换了几句话,结果可能只是尴尬一笑。但如果这事发生在 Web 服务器之间,后果就是整条请求被扭曲——攻击者可以在前端代理和后端服务器之间塞进一段"私货",而后端全然不知这条请求被篡改过。这就是 HTTP Request Smuggling。

问题根源在于 HTTP/1.1 的一个设计模糊地带:请求体长度到底由谁说了算?Content-Length 头说"我量了,刚好 42 字节",Transfer-Encoding: chunked 头说"别信它,看我分块标记来定边界"。当一个头出现在同一条请求里,前端代理看重其中一个,后端看重另一个,两人对"这条请求在哪里结束"的判断就分叉了。攻击者精心构造一段请求,让前端认为是一条完整请求,后端却把尾部切成下一条请求的开头——于是"走私"的内容就这样被后端无辜执行了。

CL-TE 和 TE-CL 是最常见的两种手法。CL-TE 场景下,前端以 Content-Length 截断,后端以 Transfer-Encoding: chunked 解析,多余的 payload 就溢出到下一个请求里。TE-CL 则反过来。更复杂的还有 TE-TE, 利用分块编码的变体写法(比如 chunked 后面加空格、大小写混写)让两台服务器对"这是不是分块编码"产生分歧。

实际危害远比"多执行一条请求"严重。走私一条 GET /admin 过去,可能绕过前端的所有鉴权;结合缓存机制,还能让有害响应被缓存命中,毒害所有后续访客——这就是缓存投毒的叠加攻击。

防御说起来简单:前端和后端对同一条请求的边界判定必须一致。但现实中你很难控制第三方 CDN 的解析行为。比较务实的做法是前端在转发前规范化请求头、剔除冲突的 Transfer-Encoding,后端拒绝同时携带两个长度指示头的请求,或者直接在全链路启用 HTTP/2——HTTP/2 的帧机制从根本上消除了这种定界歧义。



#Security