HTTP响应拆分攻击(HTTP Response Splitting)详解
今天我们来讲解一个经典的Web安全漏洞:HTTP响应拆分攻击,也被称为HTTP响应头注入攻击。这是一种相对古老但原理深刻、能帮助理解HTTP协议和Web安全基础架构的攻击手法。它的核心在于攻击者通过注入恶意的换行符,将一个HTTP响应“拆分”成两个或多个响应,从而控制后续响应的内容,实现多种攻击目的。
一、 攻击描述与核心原理
HTTP响应拆分攻击发生在Web应用程序将用户可控的数据,未经严格净化就设置为HTTP响应头的一部分时。
-
漏洞成因: 许多HTTP响应头,如
Location(用于重定向)、Set-Cookie、Content-Disposition等,其值可能包含用户输入。例如,一个常见的重定向URL可能由用户提供的参数决定:http://example.com/redirect.php?page=/dashboard。服务器端代码可能会这样构造Location头:Location: /dashboard。 -
关键注入字符: HTTP协议中,用两个特定的字符序列来分隔头部和正文,以及分隔不同的头部字段。
- CRLF: 这是
\r\n(回车+换行)的组合。在HTTP/1.1标准中,CRLF用于:- 标志一个头部字段的结束。
- 标志整个HTTP头部与HTTP正文的分隔(即一个空行
\r\n\r\n之后就是响应体)。
- CRLF: 这是
-
攻击思路: 如果攻击者能控制某个响应头的值,并成功注入
CRLF字符序列,他就能“欺骗”浏览器(或任何HTTP客户端),使其认为一个HTTP响应在注入点已经结束。注入点之后的内容,将被解释为第二个独立的HTTP响应。
二、 攻击步骤与场景剖析
让我们通过一个最常见的场景——不安全的URL重定向——来逐步拆解攻击过程。
场景设定: 一个存在漏洞的重定向页面 redirect.php。
其PHP代码可能类似于:
<?php
$page = $_GET[‘page’];
header(“Location: ” . $page); // 直接将用户输入拼接到Location头
?>
正常请求:
用户访问:http://example.com/redirect.php?page=/dashboard
服务器响应:
HTTP/1.1 302 Found
Location: /dashboard
(空行,然后是响应体…)
浏览器收到302状态码和Location头,会跳转到 /dashboard。
攻击者构造的恶意请求:
攻击者不提供普通的路径,而是注入CRLF,并精心构造第二个“响应”。
攻击者访问:
http://example.com/redirect.php?page=%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a%3Cscript%3Ealert(‘XSS’)%3C/script%3E
%0d是URL编码的\r%0a是URL编码的\n
服务器处理与响应:
脆弱的服务器代码会原封不动地将解码后的参数值拼接到Location头。所以服务器生成的完整HTTP响应(在TCP流层面)实际上是这样的:
HTTP/1.1 302 Found
Location: <-- 原始响应的开始
\r\n
Content-Length: 0\r\n
\r\n
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
\r\n
<script>alert(‘XSS’)</script>
注意,这里本应只有一个HTTP响应,但因为攻击者注入了CRLF,它被“拆分”了。
浏览器(受害者)的解析过程:
- 浏览器收到TCP数据流,开始按HTTP协议解析。
- 它首先读到状态行
HTTP/1.1 302 Found。 - 接着读到
Location:,然后立即遇到了\r\n,这标志着一个头部的结束。但Location头的值呢?协议解析器认为这个头的值是空的,因为它到第一个CRLF就结束了。 - 然后解析器读到了
Content-Length: 0和另一个CRLF。这被视为第二个响应头。 - 接着遇到了
\r\n(空行)。根据HTTP协议,空行标志着第一个HTTP响应的头部结束。 Content-Length: 0告诉浏览器,第一个响应的正文长度为0。所以浏览器认为第一个响应(一个302重定向,但目标Location为空或无效)已经接收完毕。它可能会尝试处理这个奇怪的重定向,或者忽略。- 关键步骤: 解析器不会停止,它会继续解析TCP流中后续的数据。接下来的数据是:
HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<script>alert(‘XSS’)</script>。 - 解析器会将这些数据当作第二个独立的HTTP响应来解析!这是一个状态码为200、内容类型为HTML的响应,正文包含一个恶意脚本。
最终,受害者的浏览器渲染了攻击者完全控制的第二个“响应”,导致了反射型XSS攻击的执行。
三、 攻击的影响与利用方式
HTTP响应拆分攻击的危害不仅限于XSS:
- 跨站脚本(XSS): 如上例所示,是最直接的利用方式。
- Web缓存投毒: 攻击者可以构造请求,使得被缓存(如CDN、反向代理)的是那个恶意的“第二个响应”。之后,所有请求同一资源的其他用户都会从缓存中收到这个恶意响应,造成大规模影响。
- 跨用户攻击/会话劫持: 通过注入
Set-Cookie头,攻击者可能为其他用户设置会话ID,实现会话固定攻击。 - 敏感信息泄露: 通过构造响应,诱导用户向攻击者控制的服务器发送请求,可能携带Cookies等敏感信息。
四、 防御措施
防御的核心原则是:绝对不要让用户输入控制响应头中的换行符。
-
输入验证与过滤:
- 拒绝法: 在将用户输入放入HTTP头之前,严格检查并拒绝任何包含
CR(\r, %0d)、LF(\n, %0a)的输入。这是最有效的方法。 - 编码法: 对输入进行URL编码,确保
CR、LF被编码为%0D、0A,这样它们会作为数据值的一部分,而不会被解析为控制字符。但注意,如果接收方解码不当,仍可能存在问题,因此拒绝法更安全。
- 拒绝法: 在将用户输入放入HTTP头之前,严格检查并拒绝任何包含
-
使用安全的API进行重定向:
- 现代Web框架都提供了安全的重定向方法,这些方法会对目标URL进行验证,并自动处理响应头的构造,避免字符串拼接。例如:
- PHP: 使用
header(‘Location: /safe-path’)并确保路径是硬编码或经过严格校验的,而不是直接拼接$_GET[‘param’]。 - Python (Django):
return redirect(‘/safe-path’) - Java (Spring):
return “redirect:/safe-path”
- PHP: 使用
- 现代Web框架都提供了安全的重定向方法,这些方法会对目标URL进行验证,并自动处理响应头的构造,避免字符串拼接。例如:
-
设置安全的HTTP头:
- 虽然不能防止漏洞产生,但可以减轻影响。例如,设置
Content-Security-Policy可以缓解XSS,使用HttpOnly和Secure属性的Cookie可以降低会话被盗的风险。
- 虽然不能防止漏洞产生,但可以减轻影响。例如,设置
-
保持框架和库更新:
- 现代Web开发框架通常在设计上就避免了此类漏洞。使用最新稳定版本,并遵循其安全实践。
总结
HTTP响应拆分攻击虽然现在由于框架的完善和开发者安全意识提升已不常见,但它深刻揭示了“协议级混淆”的攻击思想。理解它,不仅能帮助你应对一些遗留系统的问题,更能让你深刻认识到**“用户输入永远不可信”** 以及 “对输出到不同上下文(如HTTP头、HTML、SQL)的数据进行针对性编码或验证” 这两条安全编码的黄金法则。其原理也与HTTP请求走私等更现代的协议攻击有相通之处。