HTTP走私靶场
前言
最近看到HTTP请求走私的文章,自己先简单的过一遍,本文对HTTP2的描述不是很详细,过几天会详细写一下HTTP2走私的文章。
HTTP1
靶场:https://portswigger.net/web-security/all-labs
官方讲解:https://portswigger.net/web-security/request-smuggling
James Kettle:https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
中文翻译:https://www.yiyayiyawu.cn/archives/http-qing-qiu-zou-si
他人总结:https://tttang.com/archive/1808/
HTTP:https://developer.mozilla.org/zh-CN/docs/Web/HTTP
CL.TE
访问界面,抓包看看。
说了是CL.TE,这里改为POST请求,加上走私代码。
1 | Content-Length: 6 |
记得把Connection:
改成 keep-alive
。发送两次请求就行。
TE.CL
改为POST,加上下面的字符串,保持TCP连接。访问两次。
1 | Connection: keep-alive |
Obfuscating TE
如果第一个请求导致错误,后端服务器可能会决定关闭连接,丢弃中毒缓冲区并破坏攻击。尝试通过针对旨在接受 POST 请求的端点并保留任何预期的 GET/POST 参数来避免这种情况。
一些站点有多个不同的后端系统,前端查看每个请求的方法、URL 和标头以决定将其路由到哪里。如果受害者请求被路由到与攻击请求不同的后端,则攻击将失败。因此,“攻击”和“受害者”请求最初应尽可能相似。
先贴个可以混淆Transfer-Encoding标题的。
1 | Transfer-Encoding: xchunked |
1 这些技术中的每一种都涉及与 HTTP 规范的微妙背离。实现协议规范的现实世界代码很少绝对精确地遵守它,并且不同的实现通常会容忍来自规范的不同变化。要发现 TE.TE 漏洞,需要找到Transfer-Encoding报头的一些变体,以便只有前端或后端服务器之一处理它,而另一台服务器忽略它。
emm,前后端做到一个TE,一个CL就行。所以在后面要有0\r\n
结尾,前面要有12\r\n
对应CL。
Confirm CL.TE
计时和差异响应都可以确定,这里就按照官方WP,根据差异响应来了。
1 | Content-Length: 49 |
GET /404 HTTP/1.1
Foo: xPOST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11q=smuggling
第二次跳转404界面,原理比较好理解。(通过TE用0截断,将GET404注入缓冲区)其实那些字段是多余的,我直接复制过来的,没有那些q=smuggling&x=
也一样的。
Confirm TE.CL
1 | Connection: keep-alive |
前端为TE,还是以0\r\n
结尾。
TE.CL 攻击看起来很相似,但是需要关闭块意味着我们需要自己指定所有标头并将受害者请求放在正文中。确保前缀中的 Content-Length 略大于正文。
前面还有说过尽量保留预期的请求,不然很容易失败。我试了GET请求,也不行,环境受限。
如果站点是活动的,其他用户的请求可能会先于您的请求命中中毒套接字,这将使您的攻击失败并可能使用户感到不安。因此,此过程通常需要几次尝试,而在高流量站点上可能需要数千次尝试。请谨慎和克制,并尽可能以登台服务器为目标。
CL.TE Vulnerability
本实验涉及前端和后端服务器,前端服务器不支持分块编码。有一个管理面板
/admin
,但前端服务器阻止访问它。要解决实验室问题,请将请求走私到访问管理面板并删除用户的后端服务器
carlos
。
题目说要访问/admin,但是被堵塞了,我们先先测试CL.TE,发现能返回404,改成admin放包。
显示只允许本地访问/admin,加个Host: localhost
试试。
"error":"Duplicate header names are not allowed"
标头冲突了,我们尽量完善一下请求头。
现在我们已经确定套接字中毒是可能的,下一步是收集信息,以便我们可以发起一个消息灵通的攻击。
前端通常会附加和重写 HTTP 请求标头,例如X-Forwarded-Host和X-Forwarded-For以及许多通常具有难以猜测的名称的自定义标头。我们走私的请求可能缺少这些标头,这可能导致意外的应用程序行为和失败的攻击。
幸运的是,有一个简单的策略,我们可以部分地揭开帷幕,并了解这些隐藏的标题。这使我们可以通过自己手动添加标头来恢复功能,甚至可以启用进一步的攻击。
只需在目标应用程序上找到一个反映 POST 参数的页面,将参数打乱,使反映的参数在最后,稍微增加 Content-Length,然后走私生成的请求
1 | Connection: keep-alive |
不管什么情况下,记得补上CL/TE和CT就行。你不确定系统会给它加上什么标头,所以CT要大,上面也说过了。
发现API,直接伪造就行。删除完两个用户就搞定了。
1 | Content-Length: 137 |
TE.CL Vulnerability
还是先测一下。
1 | Connection: keep-alive |
Admin interface only available to local users
1 | Content-length: 4 |
Reveal rewriting
在许多应用程序中,前端服务器在将请求转发到后端服务器之前对其进行一些重写,通常是通过添加一些额外的请求标头。例如,前端服务器可能:
终止 TLS 连接并添加一些描述使用的协议和密码的标头;
添加X-Forwarded-For包含用户 IP 地址的标头;
根据他们的会话令牌确定用户的 ID 并添加一个标识用户的标头;或者
添加一些其他攻击感兴趣的敏感信息。
在某些情况下,如果走私请求缺少前端服务器通常添加的某些标头,那么后端服务器可能无法以正常方式处理请求,从而导致走私请求无法达到预期的效果。通常有一种简单的方法可以准确地揭示前端服务器如何重写请求。为此,需要执行以下步骤:
查找将请求参数的值反映到应用程序响应中的 POST 请求。
对参数进行随机排列,以便反映的参数最后出现在消息正文中。
将此请求走私到后端服务器,然后直接跟随一个普通请求,该请求的重写形式是您想要显示的。
查一下它重写了什么请求头,发现一个X-jxLYaN-Ip: 210.33.11.216
,改为本地访问admin试试。
Capture user’s requests
本实验涉及前端和后端服务器,前端服务器不支持分块编码。
要解决实验室问题,请将请求偷运到后端服务器,使下一个用户的请求存储在应用程序中。然后检索下一个用户的请求并使用受害用户的 cookie 访问他们的帐户。
要捕获用户的数据:其实就是把缓冲区占了,让用户访问过来的数据只能作为POST正文,拼接到comment中。
为了让我们存入缓冲区的请求能正常发出必须要设置正确的cookie,csrf也要用真实的。
这样我们的内容就被写入缓冲区了。当下一个用户再次访问的时候,就能观察到了。为了方便演示,我直接访问两次,看看能不能捕获自己的请求。
至此,我们就完成了攻击。
Reflected XSS
不过多解释,加了XSS而已。
Redirect
利用请求走私重定向到别的页面,使后端访问我们的恶意网站,调给用户,造成攻击。
PS:当路径标头被重定向到相对根目录的URL时,如果允许在路径中使用相对URL,则可能能够重定向。
1 | 正常重定向: |
1 | 恶意重定向: |
Web Cache Poisoning
验证CL.TE
请求走私这里省略。如果要得到恶意缓存,得先想办法把恶意资源加载到正常的服务器上。可以先把恶意的Host写入缓冲区,Host中包含恶意js。当我们下一次正常请求该js时,前端会去调js,发给后端时,因为缓冲区中有内容,就会调恶意的js,返回给前端,前端缓存就被污染了。
攻击没有成功,因为链接根本不对。我们要确保目录一致,要改成post而不是a。
注意:访问主页也是一样的,但是当你退出界面缓存会被清空。需要重新发包。
Web Cache Deception
Web缓存中毒和Web缓存欺骗的区别:
Web缓存中毒:攻击者使应用程序在缓存中存储一些恶意内容,并将这些内容从缓存中提供给其他应用程序用户。
Web缓存欺骗:攻击者使应用程序将属于另一个用户的一些敏感内容存储在缓存中,然后攻击者从缓存中检索这些内容。
测试不成功,简单说下原理,和缓存欺骗是一样的道理。
我们发送myaccount请求到后端的缓冲区。当一个用户访问网站的资源,例如js,png时,前端向后端请求js或png,把请求发给后端,但是收到缓冲区数据的影响,实际上是把myaccount的数据调给前端,放在缓存里面了。这时候用户的数据就能被所有访问网站的人调出来,至此,攻击完成。
防御
像往常一样,安全伴随着简单。如果您的网站没有负载平衡器、CDN 和反向代理,则此技术不是威胁。您引入的层次越多,您就越有可能受到攻击。
每当我讨论一种攻击技术时,都会有人问我 HTTPS 是否可以阻止它。一如既往,答案是否定的。也就是说,您可以通过将前端服务器配置为专门使用 HTTP/2 与后端系统通信,或者完全禁用后端连接重用来解决此漏洞的所有变体。或者,您可以确保链中的所有服务器都运行具有相同配置的相同网络服务器软件。
此漏洞的特定实例可以通过重新配置前端服务器以在将不明确的请求转发之前对其进行规范化来解决。对于不想让客户受到攻击的 CDN,这可能是唯一现实的解决方案,而 Cloudflare 和 Fastly 似乎成功地应用了它。
规范化请求不是后端服务器的选项 - 他们需要完全拒绝不明确的请求,并删除相关联的连接。由于拒绝请求比简单地规范化它们更有可能影响合法流量,因此我建议将重点放在防止通过前端服务器走私请求上。
当你的工具对你不利时,有效的防御是不可能的。大多数网络测试工具在发送请求时会自动“更正”内容长度标头,从而使请求走私成为不可能。在 Burp Suite 中,您可以使用 Repeater 菜单禁用此行为——确保您选择的工具具有等效的功能。此外,某些公司和漏洞赏金平台通过 Squid 等代理路由其测试人员的流量以进行监控。这些将破坏测试人员发起的任何请求走私攻击,确保公司对此类漏洞的覆盖率为零。
HTTP2
官方讲解1:https://portswigger.net/web-security/request-smuggling/advanced
官方讲解2:https://portswigger.net/web-security/request-smuggling/advanced/request-tunnelling
James Kettle:https://portswigger.net/research/http2
HTTP2:https://baijiahao.baidu.com/s?id=1664015403175354750
H2.CL
在本节中,我们将以您目前学到的概念为基础,教您一些更高级的HTTP 请求走私技术。我们还将介绍各种基于 HTTP/2 的攻击,这些攻击由于Burp 独特的 HTTP/2 测试功能而成为可能。如果您是 HTTP/2 的新手,请不要担心 - 我们将在接下来的过程中涵盖所有要点。
我们将特别关注:
- 常见的 HTTP/2 实现如何为请求走私 启用一系列强大的新向量,从而使许多以前安全的站点容易受到此类攻击。
- 如何使用请求走私来持续毒害响应队列,从而有效地实现全站接管。
- 即使目标根本不重用前端和后端服务器之间的 连接, 您如何使用 HTTP/2 独占输入来构建高严重性攻击。
HTTP2降级到HTTP1,和CL.TE非常相似。
1 | POST / HTTP/2 |
Response queue poisoning
https://portswigger.net/web-security/request-smuggling/advanced/response-queue-poisoning
响应队列中毒是一种强大的请求走私攻击形式,它会导致前端服务器开始将来自后端的响应映射到错误的请求。实际上,这意味着同一前端/后端连接的所有用户都是持久服务的响应,这些响应是为其他人准备的。
这是通过走私一个完整的请求来实现的,从而在前端服务器只期望一个响应时从后端引出两个响应。
响应队列中毒有什么影响?
响应队列中毒的影响通常是灾难性的。一旦队列中毒,攻击者只需发出任意后续请求即可捕获其他用户的响应。这些响应可能包含敏感的个人或企业数据,以及会话令牌等,有效地授予攻击者对受害者帐户的完全访问权限。
响应队列中毒还会造成重大的附带损害,有效地破坏通过同一 TCP 连接将流量发送到后端的任何其他用户的站点。在尝试正常浏览站点时,用户会从服务器收到看似随机的响应,这将阻止大多数功能正常工作。
如何构建响应队列中毒攻击
对于成功的响应队列中毒攻击,必须满足以下条件:
- 前端服务器和后端服务器之间的 TCP 连接被重复用于多个请求/响应周期。
- 攻击者能够成功走私一个完整的、独立的请求,该请求从后端服务器接收到自己独特的响应。
- 攻击不会导致任一服务器关闭 TCP 连接。服务器通常会在收到无效请求时关闭传入连接,因为它们无法确定请求应该在何处结束。
前端 (CL)
1 | POST / HTTP/1.1 |
后端 (TE)
1 | POST / HTTP/1.1 |
简单来说就是自己把内容存进缓冲区,因为末尾受到\r\n\r\n
的影响,当下一次用户的正常请求访问进来时被终止,缓冲区就变成了。
Remember to terminate the smuggled request properly by including the sequence \r\n\r\n after the Host header.
1 | GET / HTTP/1.1 |
后端只处理了第一个POST,GET还在缓冲区,这时候攻击者访问一下,后端处理的是上一个用户的HTTP请求,这样用户的响应就被窃取。因为前端服务器和后端服务器之间的 TCP 连接被重复用于多个请求/响应周期。那么之后访问的所有用户的数据都可能会被窃取。
CRLF injection
即使网站采取措施来防止基本的 H2.CL 或 H2.TE 攻击,例如验证content-length
或剥离任何transfer-encoding
标头,HTTP/2 的二进制格式也支持一些新颖的方法来绕过这些前端措施。
在 HTTP/1 中,您有时可以利用服务器处理独立换行 ( ) 字符的方式之间的差异\n
来走私禁止的标头。如果后端将此视为分隔符,但前端服务器不这样做,则某些前端服务器将根本无法检测到第二个标头。
1 | Foo: bar\nTransfer-Encoding: chunked |
处理完整的 CRLF ( ) 序列时不存在这种差异,\r\n
因为所有 HTTP/1 服务器都同意这会终止标头。
另一方面,由于 HTTP/2 消息是二进制的而不是基于文本的,每个标头的边界是基于明确的、预定的偏移量而不是定界符。这意味着\r\n
header 值中不再有任何特殊意义,因此可以包含在值本身中而不会导致 header 被拆分:
foo | bar\r\nTransfer-Encoding: chunked |
---|
这本身似乎相对无害,但当它被重写为 HTTP/1 请求时,***\r\n
将再次被解释为标头定界符。因此,HTTP/1 后端服务器会看到两个不同的标头:***
1 | Foo: bar |
burp这个实验内容大概就是利用CRLF配合HTTP2降级完成用户的请求捕捉。和上面的Capture user's requests
类似。
HTTP/2 request splitting
大致解释一下:在响应队列投毒的时候,我们是利用后端将一个完整的HTTP拆成两个。这里我们是利用后端拆分请求头。这样我们就不用担心不是POST请求,我们的GET请求也能实现走私。
This is also useful in cases where the
content-length
is validated and the back-end doesn’t support chunked encoding.
当前端服务器
\r\n\r\n
在降级期间附加到标头的末尾时,这有效地将走私的前缀转换为完整的请求,从而使响应队列中毒。
:method | GET |
---|---|
:path | / |
:authority | ecosystem.atlassian.net |
foo | bar Host: ecosystem.atlassian.net GET /robots.txt HTTP/1.1 X-Ignore: x |
无非就是在Foo中加入了我们的请求,相比CRLF就是多了个GET。
后端处理的请求:
1 | GET / HTTP/1.1 |
后端看到的是完整的两个请求,和响应队列投毒一样,可以一直截取用户的数据。
当然,还可以在请求头名称和请求行写入。
修补程序中的最后一个缺陷是阻止 ‘\r\n’ 但不阻止 ‘\n’ 本身的经典错误 - 后者几乎总是足以进行攻击。
Confirm HTTP request tunnelling
检测请求隧道很容易——通常的超时技术工作正常。第一个真正的挑战是确认漏洞——您可以通过发送一系列请求并查看早期请求是否影响后续请求来确认常规请求走私漏洞。不幸的是,这种技术总是无法确认请求隧道,因此很容易将漏洞误认为是误报。
我们需要一种新的确认技术。一种明显的方法是简单地走私一个完整的请求,看看你是否得到两个响应:
1 | POST / HTTP/1.1 |
如果请求主体中有HTTP1.1,那么可以确信已成功通过隧道传输第二个请求。
但是不一定是所有的场景都能测出。
前端服务器通常使用后端响应中的 Content-Length 来决定从套接字读取多少字节。这意味着即使您可以向后端发送两个请求并从中触发两个响应,前端也只会向您传递第一个不太有趣的响应。
如果怀疑存在盲请求隧道,那可以利用JHEAD等方法测试时间延迟性。如果您成功地通过执行此操作的前端服务器传送请求,则此行为可能会导致它过度读取来自后端的响应。因此,您收到的响应可能包含从响应开始到您的隧道请求的字节。
如果您向其发送HEAD
请求的端点返回的资源比您尝试读取的隧道响应短,则它可能会在您看到任何有趣的内容之前被截断。另一方面,如果返回content-length
的时间比对您的隧道请求的响应时间长,您可能会遇到超时,因为前端服务器等待额外的字节从后端到达。
1 | HEAD / HTTP/1.1 |
Leaking Internal Headers
:method | POST |
---|---|
:path | /blog |
:authority | bitbucket.org |
foo | bar Host: bitbucket.wpengine.com Content-Length: 200 s=cow |
foo=bar |
HTTP降级之后的请求
1 | POST /blog HTTP/1.1 |
此时前端和后端都认为这是一个请求,但是针对哪里是请求头,就不能分辨了。前端会把s
作为一个请求头,而内容就是
cow
SSLClientCipher: TLS_AES_128
Host: bitbucket.wpengine.com
Content-length: 7
此时我们返回的第一个响应是
1 | <title>You searched for cowSSLClientCipher: TLS_AES_128_GCM_SHA256, version=TLSv1.3, bits=128Host: bitbucket.wpengine.comSSLSessionID: X-Cluster-Client-IP: 81.132.48.250Connection: Keep-Alivecontent-length: 7 |
但是当我们尝试去访问别的敏感路径时,我们的foo=bar
就会被当成请求主体,而在在前端添加上的请求头就会拼接在bar后面,发给后端进行处理,以comment为例,我们就可以得到敏感请求头的回显。那么,就可以针对性进行利用。
Cache Poisoning
尽管请求隧道通常比经典请求走私更受限制,但有时您仍然可以构建高严重性攻击。例如,您可能能够结合我们目前已经研究过的请求隧道技术,以获得一种非常强大的 Web 缓存投毒形式。
使用非盲请求隧道,您可以有效地将一个响应的标头与另一个响应的主体混合和匹配。如果正文中的响应反映了未编码的用户输入,您可以在浏览器通常不会执行代码的上下文中 利用此行为来反射 XSS 。
:method | HEAD |
---|---|
:path | /blog/?x=dontpoisoneveryone |
:authority | bitbucket.org |
foo | bar Host: x GET /wp-admin?<svg/onload=alert(1)> HTTP/1.1 Host: bitbucket.wpengine.com |
1 | HTTP/1.1 404 Not Found |
通过 /wp-admin 触发了一个重定向,该重定向反映了 Location 标头内的用户输入,而不对其进行编码。
404时进行重定向,此时重定向加载我们已经准备好的恶意js。存入缓存中成功,则其他用户都会遭受到攻击。
防御
如果您正在设置 Web 应用程序,请避免 HTTP/2 降级 - 这是大多数此类漏洞的根本原因。相反,使用端到端的 HTTP/2。
如果您正在编写 HTTP/2 服务器,尤其是支持降级的服务器,请强制执行 HTTP/1 中存在的字符集限制 - 拒绝在标头中包含换行符、标头名称中包含冒号、请求方法中包含空格等的请求。另外,请注意,规范并不总是明确说明可能出现漏洞的位置。某些未标记的要求,如果被跳过,会给您留下一个具有严重漏洞的功能服务器。RFC 中可能也有一些强化机会。
建议 Web 开发人员放弃从 HTTP/1 继承的假设。在历史上,不对某些用户输入(如请求方法)执行大量验证就可以逃脱,但 HTTP/2 改变了这一点。