Request Smuggling vs Request Splitting in Spring Boot
Request Smuggling vs Request Splitting in Spring Boot
Request Smuggling vs Request Splitting Attack Difference Spring Boot Both attacks have CRLF in their DNA, both abuse HTTP parsing, and both can let an attacker inject content that the server treats as legitimate. That surface-level similarity is where the resemblance ends. Request smuggling desynchronizes connection state between a reverse proxy and an upstream service, poisoning the request queue. Request splitting injects newlines into a response the application is already building, forging headers or redirects. In Spring Boot deployments they hit completely different layers, have different preconditions, and need different fixes.
Spring Boot 中请求走私(Request Smuggling)与请求拆分(Request Splitting)攻击的区别在于:这两种攻击的本质都包含 CRLF(回车换行符),都滥用了 HTTP 解析机制,且都能让攻击者注入服务器视为合法的内容。然而,这种表面的相似性仅止于此。请求走私会使反向代理与上游服务之间的连接状态不同步,从而污染请求队列;而请求拆分则是将换行符注入到应用程序正在构建的响应中,从而伪造响应头或重定向。在 Spring Boot 部署中,它们影响的是完全不同的层级,具有不同的前提条件,也需要不同的修复方案。
How Smuggling and Splitting Actually Work
请求走私与请求拆分的工作原理
Request smuggling exploits disagreement about where one HTTP/1.1 request ends and the next begins. A reverse proxy (HAProxy, nginx, an AWS ALB) uses Content-Length to decide how many bytes belong to the first request. The backend Tomcat instance uses Transfer-Encoding: chunked instead, or vice versa. The bytes the proxy thinks are part of the body become a partial second request that Tomcat prepends to the next real user’s request. That’s the CL.TE variant. The TE.CL variant reverses which side gets fooled.
请求走私利用了代理服务器与后端服务器对 HTTP/1.1 请求边界判断的不一致。反向代理(如 HAProxy、nginx 或 AWS ALB)使用 Content-Length 来决定请求体的长度,而后端 Tomcat 实例可能使用 Transfer-Encoding: chunked,反之亦然。代理认为属于请求体的字节,在 Tomcat 眼中却成了第二个请求的一部分,并被拼接到下一个真实用户的请求之前。这就是 CL.TE 变体,而 TE.CL 变体则是反过来欺骗另一方。
Request splitting (also called CRLF injection) is an application-layer bug. The application takes a user-controlled value and writes it directly into an HTTP response header or into a forwarded request without stripping carriage-return/line-feed characters. When the client (or an intermediate cache) parses the response, the injected \r\n ends the current header and starts a new one the attacker controls. For a deep dive on HTTP request smuggling covering gadget chains and cache poisoning variants, the Code Review Lab lesson is worth reading alongside this article.
请求拆分(也称为 CRLF 注入)是一种应用层漏洞。应用程序获取用户可控的值,并将其直接写入 HTTP 响应头或转发的请求中,而没有过滤回车/换行字符。当客户端(或中间缓存)解析响应时,注入的 \r\n 会结束当前头部,并开启一个由攻击者控制的新头部。若想深入了解涵盖 Gadget 链和缓存投毒变体的 HTTP 请求走私,建议在阅读本文的同时参考 Code Review Lab 的相关课程。
Here are minimal wire-level examples of each:
以下是每种攻击的最小化网络层示例:
# CL.TE smuggling payload
POST / HTTP/1.1
Host: internal.example.com
Content-Length: 13
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: internal.example.com
// CRLF injection in a Spring MVC controller — splitting the Location header
// Attacker supplies: redirectUrl = "https://safe.example.com\r\nSet-Cookie: session=attacker"
@GetMapping("/redirect")
public void redirect(
@RequestParam String redirectUrl,
HttpServletResponse response) throws IOException {
// No sanitization — injects the attacker's header directly into the response
response.sendRedirect(redirectUrl);
}
When sendRedirect writes the Location header, the embedded \r\n terminates that header line and the content after it lands in the raw response as a new Set-Cookie header. Any browser or CDN that parses the response will treat the injected cookie as authoritative. The key architectural difference: smuggling requires control over a connection shared between a load balancer and a backend. Splitting requires only the ability to inject a newline into an application-controlled string. You can exploit splitting with nothing but a browser. Smuggling needs raw TCP-level access or, at minimum, a proxy that forwards ambiguous framing.
当 sendRedirect 写入 Location 头部时,嵌入的 \r\n 会终止该行,其后的内容将作为新的 Set-Cookie 头部出现在原始响应中。任何解析该响应的浏览器或 CDN 都会将注入的 Cookie 视为权威信息。核心架构差异在于:走私需要控制负载均衡器与后端之间共享的连接;而拆分仅需能够向应用程序控制的字符串中注入换行符。你仅需一个浏览器即可利用拆分漏洞,而走私则需要原始 TCP 级别的访问权限,或者至少需要一个会转发模糊帧(ambiguous framing)的代理。
Fixing Both Attacks in a Spring Boot Service
在 Spring Boot 服务中修复这两种攻击
Fixing CRLF / request splitting: Strip or reject CR and LF before any user-controlled value touches a response header. Do this at the point of use, not only at the controller layer, because the tainted value might travel through a service call before hitting HttpServletResponse.
修复 CRLF / 请求拆分: 在用户可控的值进入响应头之前,过滤或拒绝 CR 和 LF 字符。请在实际使用点进行此操作,而不仅仅是在控制器层,因为受污染的值在到达 HttpServletResponse 之前可能会经过多次服务调用。
private String sanitizeHeaderValue(String value) {
if (value == null) return "";
// Strip CR, LF, and null bytes — each can terminate a header line in
// different HTTP implementations
return value.replaceAll("[\\r\\n\\x00]", "");
}
Fixing smuggling at the embedded server layer: Tomcat’s protection against ambiguous framing has improved significantly since 9.0.31 and 10.0.0-M5. Reject requests that carry both Content-Length and Transfer-Encoding headers, which is what RFC 7230 §3.3.3 requires anyway.
在嵌入式服务器层修复走私: 自 9.0.31 和 10.0.0-M5 版本以来,Tomcat 对模糊帧的防护能力已显著增强。拒绝同时携带 Content-Length 和 Transfer-Encoding 头的请求,这本身也是 RFC 7230 §3.3.3 的要求。
# application.properties
# Reject requests with conflicting framing headers.
server.tomcat.reject-illegal-header=true
If you’re behind nginx or HAProxy, enforce normalization there too. On nginx, proxy_http_version 1.1 combined with proxy_set_header Connection "" forces a clean connection model. Switching the proxy-to-backend leg to HTTP/2 eliminates the framing ambiguity entirely because HTTP/2 frames are length-prefixed at the binary level.
如果你位于 nginx 或 HAProxy 之后,也应在这些层级强制执行规范化。在 nginx 中,使用 proxy_http_version 1.1 配合 proxy_set_header Connection "" 可以强制实现清晰的连接模型。将代理到后端的连接切换为 HTTP/2 可以彻底消除帧模糊问题,因为 HTTP/2 帧在二进制层面是长度前缀编码的。
Side-by-Side Comparison Table
对比总结表
| Dimension | Request Smuggling | Request Splitting |
|---|---|---|
| 维度 | 请求走私 | 请求拆分 |
| Attack surface | Connection shared between proxy and backend | Any response header or forwarded request built from user input |
| 攻击面 | 代理与后端之间共享的连接 | 任何由用户输入构建的响应头或转发请求 |
| Required precondition | Proxy and backend disagree on framing headers | Application writes unsanitized user input to headers |
| 必要前提 | 代理与后端对帧头判断不一致 | 应用程序将未经清洗的用户输入写入头部 |
| Protocol layer | Transport/framing (HTTP/1.1 connection semantics) | Application layer |
| 协议层 | 传输/帧层(HTTP/1.1 连接语义) | 应用层 |