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 value

Node.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防护纳入代码审查和安全开发流程中,从开发阶段就把这个漏洞堵死。

最后提醒一句,安全防护是持续的过程,不是一次性的工作。随着攻击手法的演进,防御策略也需要不断更新。保持对安全动态的关注,定期复查代码,才能真正守住网站安全的底线。