HTTP头注入和HTTP响应拆分是Web应用中两种非常危险的安全漏洞,它们的本质都是攻击者通过在用户输入中插入特殊字符(如CRLF换行符),篡改HTTP响应头的结构,从而实现恶意重定向、缓存投毒、会话劫持甚至XSS攻击。修复这类漏洞的核心方法就是:对所有用户输入进行严格的过滤和验证,禁止输入中出现CR(\r)和LF(\n)字符,同时在服务端设置安全的HTTP响应头,如Content-Security-Policy、X-Content-Type-Options等。下面我会从漏洞原理、攻击场景、具体修复方案和最佳实践四个层面,把这件事讲透。

一、HTTP头注入漏洞到底是什么

HTTP协议中,响应头和响应体之间是用一个空行(即\r\n\r\n)来分隔的。HTTP头注入漏洞的产生原因是:当Web应用把用户可控的数据直接拼接到HTTP响应头中,而没有对换行符进行过滤时,攻击者就可以通过注入\r\n来伪造新的响应头字段,甚至插入一个完整的恶意响应体。比如一个网站的重定向功能,代码可能是这样的:

Location: http://example.com/redirect?url=用户输入

如果用户输入的是 "http://evil.com%0d%0aSet-Cookie: session=hacked",那么最终的响应头就会变成:

Location: http://example.com/redirect?url=http://evil.com
Set-Cookie: session=hacked

这样攻击者就成功注入了一个Set-Cookie头,可以覆盖用户的会话Cookie。这种漏洞在PHP、Java、Python、Node.js等各种后端语言中都可能出现,关键在于开发者是否对用户输入做了安全处理。

二、HTTP响应拆分攻击的原理和危害

HTTP响应拆分(HTTP Response Splitting)是头注入的一种进阶利用方式。攻击者不仅注入响应头,还能在响应中插入第二个完整的HTTP响应。这意味着攻击者可以在同一个TCP连接中返回两个响应,第一个是正常的,第二个是恶意的。这种攻击在缓存服务器、代理服务器场景下危害极大,因为恶意响应可能被缓存下来,之后所有访问该URL的用户都会收到被投毒的内容。

具体来说,攻击者构造的输入可能包含两组\r\n\r\n,第一组结束正常响应,第二组开始一个全新的恶意响应。比如在一个使用了代理缓存的网站上,攻击者可以让缓存服务器存储一个包含恶意JavaScript的页面,后续用户访问时直接执行这段脚本,造成大规模XSS攻击。

三、漏洞产生的常见代码场景

以下是几种典型的漏洞代码模式,开发者需要重点排查:

场景一:直接拼接重定向URL。PHP代码示例:

<?php
$redirect_url = $_GET['url'];
header("Location: " . $redirect_url);
?>

场景二:设置自定义响应头时使用用户输入。Java代码示例:

String userAgent = request.getHeader("User-Agent");
response.setHeader("X-Custom-Header", userAgent);

场景三:Node.js中拼接响应头:

const url = req.query.redirect;
res.setHeader('Location', url);
res.end();

以上三种写法都没有对输入中的\r和\n字符进行过滤,都存在头注入风险。

四、具体修复方案:输入过滤与输出编码

修复HTTP头注入和响应拆分漏洞,需要从输入验证、输出编码、安全头设置三个维度同时入手。

第一步:严格过滤用户输入。所有可能被放入HTTP头的用户数据,都必须移除或拒绝包含CR(\r,ASCII 13)和LF(\n,ASCII 10)的输入。以下是各语言的实现方式:

PHP修复代码:

<?php
function sanitize_header_input($input) {
    // 移除所有CR和LF字符
    $sanitized = str_replace(array("\r", "\n"), '', $input);
    // 如果输入被修改过,说明包含危险字符,可以选择拒绝或记录日志
    if ($sanitized !== $input) {
        error_log("Potential header injection attempt: " . $input);
        return false;
    }
    return $sanitized;
}

$redirect_url = sanitize_header_input($_GET['url']);
if ($redirect_url !== false) {
    header("Location: " . $redirect_url);
} else {
    header("Location: /error");
}
?>

Python修复代码:

import re

def sanitize_header_input(input_str):
    if re.search(r'[\r\n]', input_str):
        raise ValueError("Invalid input: contains CRLF characters")
    return input_str

# 使用示例
try:
    url = sanitize_header_input(request.args.get('url', ''))
    response.headers['Location'] = url
except ValueError:
    response.headers['Location'] = '/error'

Java修复代码:

public static String sanitizeHeaderInput(String input) {
    if (input == null) return null;
    if (input.contains("\r") || input.contains("\n")) {
        throw new IllegalArgumentException("Invalid input contains CRLF");
    }
    return input;
}

第二步:使用白名单验证。与其过滤危险字符,更安全的做法是只允许符合预期格式的输入。比如重定向URL,应该只允许http或https开头的合法URL格式:

<?php
function validate_redirect_url($url) {
    if (!preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/.*)?$/', $url)) {
        return false;
    }
    return $url;
}
?>

第三步:对输出进行URL编码。即使输入通过了验证,在放入HTTP头之前也建议进行URL编码,作为防御纵深:

<?php
$safe_url = urlencode($redirect_url);
header("Location: " . $safe_url);
?>
五、设置安全HTTP响应头作为纵深防御

除了修复代码层面的漏洞,还需要在Web服务器和应用层面配置安全的HTTP响应头,即使头注入被利用,也能降低危害:

Content-Security-Policy(CSP):限制页面可以加载和执行的资源,即使攻击者注入了恶意脚本,CSP也能阻止其执行。

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123';

X-Content-Type-Options: nosniff:防止浏览器MIME类型嗅探,阻止攻击者通过注入恶意内容类型来执行脚本。

X-Content-Type-Options: nosniff

X-Frame-Options: DENY:防止页面被嵌入iframe,抵御点击劫持攻击。

X-Frame-Options: DENY

Strict-Transport-Security(HSTS):强制HTTPS连接,防止中间人攻击中的头注入利用。

Strict-Transport-Security: max-age=31536000; includeSubDomains

在Nginx中配置这些头的示例:

server {
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Frame-Options "DENY";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
}
六、Web服务器和框架层面的防护配置

很多现代Web框架已经内置了对HTTP头注入的防护机制,但需要正确配置才能生效。比如在Apache中,可以使用mod_headers模块来限制响应头的注入;在Tomcat中,可以配置strict-http-header-validation来自动拒绝包含非法字符的请求。

Nginx层面可以通过限制请求头大小和内容来降低风险:

server {
    large_client_header_buffers 4 8k;
    # 限制单个请求头的最大长度
    client_header_buffer_size 1k;
}

同时建议在WAF(Web应用防火墙)层面配置规则,拦截包含\r\n序列的请求参数,这是一道非常有效的外部防线。

七、测试验证与持续监控

修复完成后,必须进行安全测试来验证漏洞是否真正消除。可以使用以下方法:

手动测试:在所有接受用户输入并用于HTTP头的参数中,尝试注入%0d%0a、\r\n、%0a等变体,观察响应是否被正确拒绝或过滤。

自动化扫描:使用OWASP ZAP、Burp Suite等安全工具的主动扫描功能,检测HTTP头注入漏洞。这些工具会自动尝试各种编码变体,比手动测试更全面。

代码审计:定期对涉及HTTP头操作的代码进行安全审计,特别是在代码更新和功能迭代时,新增的重定向、自定义头设置等功能都需要重点审查。

日志监控:在生产环境中开启详细的访问日志记录,监控是否有包含异常字符的请求被频繁提交,这可能是攻击者在进行探测。

八、总结与核心建议

HTTP头注入和响应拆分漏洞虽然原理不复杂,但危害范围广、利用门槛低,是Web安全中必须优先修复的高危问题。核心修复思路就三点:第一,永远不要信任用户输入,所有放入HTTP头的数据必须经过严格过滤和白名单验证;第二,使用URL编码等输出编码技术增加防御层次;第三,配置完善的安全HTTP响应头作为纵深防御。把这三层做到位,基本就能杜绝此类漏洞的利用。安全不是一次性的工作,而是需要在开发流程、代码审查、部署配置、持续监控各个环节都保持警惕。