在复杂的网络环境下,不同的服务器以不同的方式实现RFC标准,利用前后端服务器对数据包的边界了解不一致的情况下,向一个请求数据包中插入下一个请求数据包的一部分,在前端服务器角度看来,它属于一个完整的请求,而在后端服务器看来,它属于两次请求,前端请求的一部分被后端服务器解释为下一个请求的开始。因此,它使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

相关概念

keep-alive 与 pipeline

Keep-Alive,就是在 HTTP 请求中增加一个特殊的请求头 Connection: Keep-Alive,告诉服务器,接收完这次 HTTP 请求后,不要关闭 TCP 链接,后面对相同目标服务器的 HTTP 请求,重用这一个 TCP 链接,这样只需要进行一次 TCP 握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。这个特性在 HTTP1.1 中是默认开启的。

有了 Keep-Alive 之后,后续就有了 Pipeline,在这里呢,客户端可以像流水线一样发送自己的 HTTP 请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。现如今,浏览器默认是不启用 Pipeline 的,但是一般的服务器都提供了对 Pipleline 的支持。

CL 与 TE

CL 和 TE 即是 Content-Length 和 Transfer-Encoding 请求头(严格来讲前者是个实体头,为了方便就都用请求头代指)。这里比较有趣的是 Transfer-Encoding(HTTP/2 中不再支持),指定用于传输请求主体的编码方式,可以用的值有 chunked/compress/deflate/gzip/identity 。

chunked: Data is sent in a series of chunks. The Content-Length header is omitted in this case and at the beginning of each chunk you need to add the length of the current chunk in hexadecimal format, followed by ‘\r\n’ and then the chunk itself, followed by another ‘\r\n’. The terminating chunk is a regular chunk, with the exception that its length is zero. It is followed by the trailer, which consists of a (possibly empty) sequence of entity header fields.

设置了 Transfer-Encoding: chunked 后,请求主体按一系列块的形式发送,并将省略 Content-Length。在每个块的开头需要用十六进制数指明当前块的长度,数值后接 \r\n(占 2 字节),然后是块的内容,再接 \r\n 表示此块结束。最后用长度为 0 的块表示终止块。终止块后是一个 trailer,由 0 或多个实体头组成,可以用来存放对数据的数字签名等

1
[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n

在计算长度时有一些需要注意的原则:

1
2
Content-Length 需要将请求主体中的 \r\n 所占的 2 字节计算在内,而块长度要忽略块内容末尾表示终止的 \r\n
请求头与请求主体之间有一个空行,是规范要求的结构,并不计入 Content-Length

走私原因

在HTTP1.1后,增加了一个特殊的请求头Connection: Keep-Alive,建立tcp持续通道,进行一次tcp握手,就能传送多个请求。但这样子只能是请求一次响应一次。为了提高数据传输的效率,减少阻塞。后来就有了HTTP Pipelining(管线化)字段,它是将多个http请求批量提交,而不用等收到响应再提交的异步技术。如下图就是使用Pipelining和非Pipelining

这意味着前端与后端必须短时间内对每个数据包的边界大小达成一致,否则,攻击者就可以构造发送一个特殊的数据包,在前端看来它是一个请求,但在后端却被解释为了两个不同的HTTP请求。这就导致攻击者可以在下一个用户发送的合法数据包前恶意添加内容。如图,走私的内容(“前缀”),以橙色突出显示:

假设前端考虑的是内容长度头部(Content-Length)值作为数据包结束的边界,后端优先考虑的是Transfer-Encoding头部。那么从后端角度看,如下图蓝色部份字体属于一个数据包,而红色部份字体属于下一个数据包的开始部份。这样就成功从前端“走私”了一个数据包

走私方式

CL不为0的GET请求

假设前端代理服务器允许GET请求携带请求体,而后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的 Content-Length头,不进行处理。这就有可能导致请求走私。

比如发送下面请求

1
2
3
4
5
GET / HTTP/1.1
Host:example.com
Content-Length:44
GET /socket HTTP/1.1
Host: example.com

前端服务器通过读取Content-Length,确认这是个完整的请求,然后转发到后端服务器,而后端服务器因为不对Content-Length进行判断,由于Pipeline的存在,它认为这是两个请求,分别为

第一个

1
2
GET / HTTP/1.1
Host: example.com

第二个

1
2
GET /socket HTTP/1.1
Host: example.com

则相当于走私了请求

CL-CL

在RFC7230规范中,规定当服务器收到的请求中包含两个 Content-Length,而且两者的值不同时,需要返回400错误。但难免会有服务器不严格遵守该规范。假设前端和后端服务器都收到该类请求,且不报错,其中前端服务器按照第一个Content-Length的值对请求进行为数据包定界,而后端服务器则按照第二个Content-Length的值进行处理。

这时攻击者可以恶意构造一个特殊的请求,

1
2
3
4
5
6
POST / HTTP/1.1
Host: example.com
Content-Length: 6
Content-Length: 5
123
A

CDN服务器获取到的数据包的长度6,将上述整个数据包转发给后端的服务器,而后端服务器获取到的数据包长度为5。当读取完前5个字符后,后端服务器认为该请求已经读取完毕,然后发送出去。而此时的缓冲区去还剩余一个字母 A,对于后端服务器来说,这个 A是下一个请求的一部分,但是还没有传输完毕。此时恰巧有一个其他的正常用户对服务器进行了请求,则该A字母会拼凑到下一个正常用户请求的前面,攻击在此展开。

CL-TE

前置服务器认为 Content-Length 优先级更高(或者根本就不支持 Transfer-Encoding ) ,后端认为 Transfer-Encoding 优先级更高。

举个栗子,假如发送的请求如下:

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: 1.com
Content-Length: 6
Transfer-Encoding: chunked

0

A

前置服务器根据 Content-Length: 6 判断出这是一个完整的请求,于是整体转发到后端服务器,但后端根据 Transfer-Encoding: chunked 将请求主体截断到 0\r\n\r\n 并认为一个完整的请求结束了,最后剩下的 A 就被认为是下一个请求的一部分,留在缓冲区中等待剩余的请求。如果此时其他用户此时发送了一个 GET 请求,就会与 A 拼接成一个畸形的 AGET,造成服务器解析异常:

1
2
3
AGET / HTTP/1.1
Host: 1.com
....

在做之前记得要把 BurpSuite 的自动更新 Content-Length 功能取消了。
注意:需要发送两次请求

实验:https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te

进入靶场抓包,修改为POST请求方式,关闭burp suite自动更新Content-Length功能

修改数据包,添加 Transfer-Encoding: chunked,修改数据包Content-Length的值。
Content-Length为10的原因为回车占据两字节,回车+0+回车+HELLO共10字节,接着提交,返回正常。

再次提交,提示:”Unrecognized method HELLOPOST”

TE-CL

前置服务器认为 Transfer-Encoding 优先级更高,后端认为 Content-Length 优先级更高(或者不支持 Transfer-Encoding )。

以如下请求为例:

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

17
POST /rook1e HTTP/1.1

0
[空白行]
[空白行]

前置服务器将其分块传输,其实就一个长度为 23 的块 POST /rook1e HTTP/1.1\r\n,但后端服务器根据 Content-Length: 4 截取到 17\r\n 即认为是一个完整的请求,剩下的留在缓冲区中等待剩余内容,若此时由用户发送了一个 GET,即被拼接成了一个 POST /rook1e 走私请求。

1
2
3
4
5
6
POST /rook1e HTTP/1.1

0

GET / HTTP/1.1
....

连发两次包,可以发现后端服务器找不到 /rook1e 而返回 404。

实验: https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl

TE-TE

前端服务器处理第一个Transfer-Encoding请求头,后端服务器处理第二个Transfer-Encoding请求头。

构造数据包

1
2
3
4
5
6
Host:example.com
Content-length: 3
Transfer-Encoding: chunked
Transfer-encoding: error
chunkedcode
0

这里是用了两个Transfer-Encoding 字段,并且第二个 TE 字段值为错误值,这里 前端服务器选择对第一个 Transfer-Encoding进行处理,整个请求正常,原封不动转发给后端服务器,而后端服务器则以第二个Transfer-Encoding 字段进行优先处理,而第二个Transfer-Encoding 字段非标准值,根据RPC规范,则会取Content-Length字段进行处理,这样这个请求就会被拆分为两个请求。

在做之前记得要把 BurpSuite 的自动更新 Content-Length 功能取消了。

注意:需要发送两次请求

实验:https://portswigger.net/web-security/request-smuggling/lab-ofuscating-te-header

例题

https://xz.aliyun.com/t/6654#toc-5

参考文章:

http走私
浅析HTTP走私攻击
浅谈HTTP请求走私
协议层的攻击——HTTP请求走私
从一道题到协议层攻击之HTTP请求走私
从一道题深入HTTP协议与HTTP请求走私