HTTP响应拆分(HTTP Response Splitting)是一种利用CRLF(Carriage Return Line Feed,即回车换行符\r\n)注入来篡改HTTP响应头的攻击技术。攻击者通过在用户输入中注入\r\n字符序列,欺骗服务器将恶意内容写入HTTP响应头,从而实现缓存投毒、XSS跨站脚本、会话劫持等严重安全后果。防御的核心就是对所有用户输入进行严格的CRLF过滤,确保任何\r和\n字符都不会被原样写入HTTP响应头中。下面我会从攻击原理、实际危害、过滤策略、代码实现和最佳实践几个层面,把这个问题彻底讲清楚。
一、HTTP响应拆分攻击的底层原理
HTTP协议的响应结构由状态行、响应头和响应体三部分组成,各部分之间用CRLF(\r\n)分隔。正常情况下,服务器会把用户输入经过处理后放入响应体中,而不是响应头中。但如果服务器直接把未经处理的用户输入拼接到响应头字段里,攻击者就可以通过注入\r\n来"提前结束"当前响应头,并"开始"一个新的响应头或响应体。
举个最简单的例子:假设服务器代码把用户输入的name参数直接写入Set-Cookie头:
Set-Cookie: sessionid=abc123; name=用户输入内容
如果攻击者输入的内容是:
hacker\r\nSet-Cookie: admin=true\r\n\r\n<script>alert(1)</script>
最终服务器发出的HTTP响应就变成了:
HTTP/1.1 200 OK Set-Cookie: sessionid=abc123; name=hacker Set-Cookie: admin=true <script>alert(1)</script>
看到没有?攻击者成功注入了一个新的Cookie,还在响应体里塞了一段脚本。这就是HTTP响应拆分的本质——利用CRLF字符打破响应结构的边界。
二、CRLF注入的具体危害有哪些
很多人觉得这只是个小漏洞,实际上它的危害链条非常长。第一,会话劫持。攻击者可以通过注入Set-Cookie覆盖原有会话标识,直接接管其他用户的账号。第二,缓存投毒。如果响应被中间代理缓存,恶意内容会被分发给所有访问同一页面的用户,影响面极大。第三,XSS攻击。注入的脚本会在受害者浏览器中执行,窃取信息、篡改页面。第四,HTTP头部注入导致的信息泄露,比如注入Location头实现开放重定向钓鱼。
三、CRLF过滤的核心策略
防御HTTP响应拆分,本质上就是对所有可能写入HTTP响应头的用户输入做CRLF过滤。具体策略有以下几种:
1. 输入验证(白名单策略)
最安全的方式是只允许预期格式的字符通过。比如用户名只允许字母、数字和下划线,那就用正则表达式严格匹配:
^[a-zA-Z0-9_]+$
任何包含\r、\n或其他特殊字符的输入直接拒绝,从源头上杜绝注入可能。
2. 输入清理(黑名单策略)
如果业务场景必须允许某些特殊字符,那就对\r和\n进行转义或移除。常见做法是把\r\n替换为空字符串,或者编码为%0D%0A:
// PHP示例
$clean_input = str_replace(array("\r", "\n"), '', $user_input);
// Java示例
String cleanInput = userInput.replaceAll("[\\r\\n]", "");3. 输出编码
在将数据写入HTTP响应头之前,进行URL编码或Base64编码。即使输入中包含CRLF字符,编码后也不会被解析为换行符。但要注意,编码方式必须与接收端解码方式一致,否则会导致数据损坏。
4. 使用安全的API
很多现代Web框架已经内置了防护机制。比如在设置Cookie时,使用框架提供的安全API而不是手动拼接字符串。以Java Servlet为例:
// 不安全的写法
response.setHeader("Set-Cookie", "session=" + userInput);
// 安全的写法
Cookie cookie = new Cookie("session", userInput);
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.addCookie(cookie);框架的Cookie对象会自动处理特殊字符,避免CRLF注入。
四、不同语言环境下的CRLF过滤实现
PHP环境
PHP中header()函数如果直接使用用户输入,非常危险。正确做法:
<?php
function sanitize_header_value($value) {
// 移除所有CRLF字符
$value = str_replace(array("\r", "\n"), '', $value);
// 同时过滤其他危险字符
$value = preg_replace('/[^\x20-\x7E]/', '', $value);
return $value;
}
$name = sanitize_header_value($_GET['name']);
header("X-Custom-Header: " . $name);
?>Python环境
import re
def sanitize_header(value):
# 移除CRLF
value = value.replace('\r', '').replace('\n', '')
# 只保留可打印ASCII字符
value = re.sub(r'[^\x20-\x7e]', '', value)
return valueNode.js环境
function sanitizeHeader(value) {
return value.replace(/[\r\n]/g, '').replace(/[^\x20-\x7E]/g, '');
}五、容易被忽略的CRLF注入点
很多开发者只关注明显的输入点,比如表单字段、URL参数,但实际上以下位置同样危险:
第一,HTTP请求头本身。User-Agent、Referer、X-Forwarded-For等请求头都可能被客户端篡改,如果服务器把这些值原样写入响应头,就存在风险。第二,URL重定向。如果用用户输入拼接Location头进行跳转,攻击者可以注入CRLF实现响应拆分。第三,文件名下载。Content-Disposition头中的filename参数如果来自用户输入,同样需要过滤。第四,日志记录后的二次利用。有些系统把请求参数写入日志,后续又从日志读取并写入响应,形成间接注入。
六、纵深防御:不要只依赖过滤
CRLF过滤是第一道防线,但不能只靠这一招。完整的防护体系应该包括:
第一,部署WAF(Web应用防火墙)。WAF可以检测请求中的CRLF注入特征并拦截。第二,启用HTTP Only和Secure标志的Cookie,即使Cookie被注入也难以被JavaScript读取。第三,实施内容安全策略(CSP),限制页面中脚本的执行来源,降低XSS危害。第四,定期进行安全审计和渗透测试,用自动化工具扫描CRLF注入漏洞。第五,保持框架和组件更新,很多历史版本的框架本身就存在CRLF注入的bug。
七、实战检测方法
如果你想检测自己的网站是否存在HTTP响应拆分漏洞,可以用以下方法:在URL参数或表单字段中输入%0D%0A(URL编码的CRLF),然后用抓包工具查看响应。如果响应头中出现了额外的头字段或者响应体提前出现,就说明存在漏洞。也可以直接输入\r\n的十六进制编码进行测试。注意测试时要用合法的测试环境,不要对生产系统造成影响。
八、总结与建议
HTTP响应拆分和CRLF注入虽然不像SQL注入那样广为人知,但它的危害一点都不小,尤其在缓存环境和会话管理场景下。防御的关键就是三点:严格的输入验证、全面的CRLF字符过滤、使用框架提供的安全API。不要试图用一个正则表达式解决所有问题,要根据具体的业务场景选择合适的防护策略。同时,把CRLF防护纳入代码审查和安全开发流程中,从开发阶段就把这个漏洞堵死。
最后提醒一句,安全防护是持续的过程,不是一次性的工作。随着攻击手法的演进,防御策略也需要不断更新。保持对安全动态的关注,定期复查代码,才能真正守住网站安全的底线。
