> 技术文档 > Web 安全之 HTTP 响应截断攻击详解_url编码%0d%0a

Web 安全之 HTTP 响应截断攻击详解_url编码%0d%0a

这不是危言耸听。
在一次安全审计中,某电商平台发现:
用户访问首页后,自动跳转到了赌博网站
但代码没被篡改,服务器没被入侵,日志一切正常。

最终追查发现——
罪魁祸首,竟是一个 %0d%0a(回车+换行)的URL参数。

这就是鲜为人知却极其危险的:HTTP 响应截断攻击
它不靠漏洞提权,不靠暴力破解,而是用“文本注入”的方式,让服务器自己“说出”恶意内容

今天,我们就来揭开这场“语言级”攻击的真相。

一、你的响应头,可能已经被“切开”了

我们每天都在和 HTTP 打交道:

  • 浏览器请求页面,
  • 服务器返回数据,
  • 一切看似自然流畅。

但你有没有想过——
HTTP 响应,其实是“拼”出来的?

服务器会把响应头和响应体像“三明治”一样组合起来:

HTTP/1.1 200 OKContent-Type: text/htmlSet-Cookie: user=alice...

其中,换行符 \\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%0aalert(1)

服务器就会生成:

HTTP/1.1 302 FoundLocation: https://safe.comContent-Type: text/htmlalert(1)

浏览器收到后,会认为这是两个独立的HTTP响应

  1. 第一个:重定向到 safe.com(合法)
  2. 第二个:一个包含恶意脚本的页面(攻击者注入)

虽然现代浏览器对重定向中的响应体处理较严格,但在非重定向场景中,这种攻击可以直接生效。

三、更可怕的实战:Cookie + XSS = 会话劫持

来看一个更真实、更危险的案例。

场景:设置用户名并写入 Cookie

某网站允许用户自定义昵称,并通过响应头设置 Cookie:

Set-Cookie: username=用户输入的昵称

攻击者注册昵称为:

Alice%0d%0a%0d%0a

服务器生成的响应变成:

HTTP/1.1 200 OKSet-Cookie: username=Alice...

用户的浏览器会:

  • 忽略第一个“空响应”,
  • 执行第二个响应体中的恶意脚本。

结果:

  • 用户毫无察觉,
  • 却已加载了黑客的 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 的请求;
  • 特别关注 CookieLocationReferer 等头字段的输入源。

2. 监控异常响应

  • 设置告警:短时间内大量 302 重定向或 Set-Cookie 异常;
  • 定期审计日志,查找可疑的 URL 编码参数。

安全,藏在最不起眼的字符里

我们总以为,安全是防火墙、是加密、是漏洞扫描。
但真正的风险,往往藏在一行代码、一个换行符、一次不规范的输入处理中。

HTTP 响应截断攻击提醒我们:

在Web世界里,每一个字符都可能是武器。

作为开发者,不要问“谁会这么干”;
而要问:“如果有人这么干,我的系统会不会崩?”

防御的本质,不是预测攻击,而是杜绝可能性。