从 2023 年 8 月 25 日开始,我们开始注意到一些异常严重的 HTTP 攻击攻击了很多我们的用户。 幸运的是我们的自动化 DDoS 系统检测到并缓解了这些攻击。 然而,没过多久,它们的规模就开始达到破纪录的水平,并最终达到每秒略高于 2.01 亿个请求的峰值。 这相当于我们之前有记录以来最大攻击规模的三倍。而更令人担忧的是,攻击者能够利用仅由 20,000 台机器组成的僵尸网络发起此类攻击。 而如今的僵尸网络规模可达数十万或数百万台机器。 整个web网络通常每秒处理 10-30 亿个请求,因此使用此方法可以将整个web网络的请求数量等级集中在少数目标上,而这并非不可想象。
如果您正在遭受类似攻击或想寻求增强安全防护支持
检测和缓解情况概述
这是一种规模空前的新型攻击手段,Cloudflare 现有的保护措施在很大程度上能够抵御这种攻击的冲击。 虽然最初我们看到了对客户流量的一些影响(在第一波攻击期间影响了大约 1% 的请求),但今天我们已经能够改进我们的缓解方法,以阻止任何 针对Cloudflare 客户的攻击,也不影响我们自身的系统正常运行。
我们注意到这些攻击的同时,另外两个主要行业参与者——谷歌和 AWS——也看到了同样的情况。 我们致力于强化 Cloudflare 的系统,以确保今天我们的所有客户都能免受这种新的 DDoS 攻击方法的影响,而不会对客户造成任何影响。 我们还与 Google 和 AWS 合作,向受影响的供应商和关键基础设施提供商协调披露此次攻击。
这种攻击是通过滥用 HTTP/2 协议的某些功能和服务器实现细节而实现的。 由于该攻击利用了 HTTP/2 协议中的潜在弱点,因此我们相信任何实施了 HTTP/2 的供应商都将受到攻击。 这包括所有现代网络服务器。 我们与 Google 和 AWS 一起向 Web 服务器供应商披露了攻击方法,我们预计他们将实施补丁。 与此同时,最好的防御措施是在任何 Web 或 API 服务器前面使用 Cloudflare 等 DDoS 缓解服务。
这篇文章深入探讨了 HTTP/2 协议的详细信息、攻击者用来生成这些大规模攻击的方法,以及我们为确保所有客户受到保护而采取的缓解策略。 我们希望通过发布这些详细信息,其他受影响的网络服务器和服务将获得实施缓解策略所需的信息。 此外,HTTP/2 协议标准团队以及致力于未来 Web 标准的团队可以更好地设计它们以防止此类攻击。
RST攻击细节
HTTP 是为 Web 提供支持的应用程序协议。 HTTP 语义对于所有版本的 HTTP 都是通用的 — 整体架构、术语和协议方面,例如请求和响应消息、方法、状态代码、标头和尾部字段、消息内容等等。 每个单独的 HTTP 版本都定义了如何将语义转换为“有线格式”以通过 Internet 进行交换。 例如,客户端必须将请求消息序列化为二进制数据并发送,然后服务器将其解析回它可以处理的消息。
HTTP/1.1 使用文本形式的序列化。 请求和响应消息作为 ASCII 字符流进行交换,通过可靠的传输层(如 TCP)发送,使用以下格式(其中 CRLF 表示回车和换行):
HTTP-message = start-line CRLF
( field-line CRLF )
CRLF
【 message-body 】
例如,对于 https://blog.cloudflare.com/ 的一个非常简单的 GET 请求在线路上将如下所示:
GET / HTTP/1.1 CRLFhost:blog.cloudflare.comCRLF
响应将如下所示:
HTTP/1.1 200 OK CRLFServer: cloudflareCRLFContent-Length: 100CRLFtext/html; charset=UTF-8CRLF<100 bytes of data>
这种格式在线路上构造消息,这意味着可以使用单个 TCP 连接来交换多个请求和响应。 但是,该格式要求每条消息都完整发送。 此外,为了正确地将请求与响应关联起来,需要严格的排序; 这意味着消息是串行交换的并且不能多路复用。 https://blog.cloudflare.com/ 和 https://blog.cloudflare.com/page/2/ 的两个 GET 请求将是:
GET / HTTP/1.1 CRLFHost: blog.cloudflare.comCRLFGET /page/2 HTTP/1.1 CRLFHost: blog.cloudflare.comCRLF
而response是:
HTTP/1.1 200 OK CRLFServer: cloudflareCRLFContent-Length: 100CRLFtext/html; charset=UTF-8CRLF<100 bytes of data>HTTP/1.1 200 OK CRLFServer: cloudflareCRLFContent-Length: 100CRLFtext/html; charset=UTF-8CRLF<100 bytes of data>
网页需要比这些示例更复杂的 HTTP 交互。 访问 Cloudflare 博客时,您的浏览器将加载多个脚本、样式和媒体资产。 如果您使用 HTTP/1.1 访问首页并决定快速导航到第 2 页,您的浏览器可以从两个选项中进行选择。 在第 2 页甚至可以启动之前,等待您不再需要的页面的所有排队响应,或者通过关闭 TCP 连接并打开新连接来取消正在进行的请求。 这些都不太实用。 浏览器倾向于通过管理 TCP 连接池(每个主机最多 6 个)并在池上实现复杂的请求分派逻辑来解决这些限制。
HTTP/2 解决了 HTTP/1.1 的许多问题。 每个 HTTP 消息都被序列化为一组 HTTP/2 帧,这些帧具有类型、长度、标志、流标识符 (ID) 和有效负载。 流 ID 清楚地表明线路上的哪些字节适用于哪个消息,从而允许安全的多路复用和并发。 流是双向的。 客户端发送帧,服务器使用相同的 ID 回复帧。
在 HTTP/2 中,我们对 https://blog.cloudflare.com 的 GET 请求将通过流 ID 1 进行交换,客户端发送一个 HEADERS 帧,服务器使用一个 HEADERS 帧进行响应,后跟一个或多个 DATA 帧。 客户端请求始终使用奇数流 ID,因此后续请求将使用流 ID 3、5 等。 可以以任何顺序提供响应,并且来自不同流的帧可以交织。
流复用和并发是 HTTP/2 的强大功能。 它们可以更有效地使用单个 TCP 连接。 HTTP/2 优化了资源获取,尤其是与优先级结合使用时。 另一方面,与 HTTP/1.1 相比,使客户端能够轻松启动大量并行工作可能会增加对服务器资源的峰值需求。 这是一个明显的拒绝服务向量。
为了提供一些防护,HTTP/2 提供了最大活动并发流的概念。 SETTINGS_MAX_CONCURRENT_STREAMS 参数允许服务器通告其并发限制。 例如,如果服务器规定限制为 100,则任何时候只能有 100 个请求处于活动状态。 如果客户端尝试打开超过此限制的流,则服务器必须使用 RST_STREAM 帧拒绝它。 流拒绝不会影响连接上的其他正在进行的流。
真实的故事有点复杂。 流有生命周期。 下图是 HTTP/2 流状态机的示意图。 客户端和服务器管理自己的流状态视图。 HEADERS、DATA 和 RST_STREAM 帧在发送或接收时会触发转换。 虽然流状态的视图是独立的,但它们是同步的。
HEADERS 和 DATA 帧包含 END_STREAM 标志,当设置为值 1(真)时,可以触发状态转换。
让我们通过一个没有消息内容的 GET 请求示例来解决这个问题。 客户端以 HEADERS 帧的形式发送请求,并将 END_STREAM 标志设置为 1。客户端首先将流从空闲状态转换为打开状态,然后立即转换为半关闭状态。 客户端半关闭状态意味着它不能再发送HEADERS或DATA,只能发送 WINDOW_UPDATE、PRIORITY或RST_STREAM帧。 然而它可以接收任何帧。
一旦服务器接收并解析了 HEADERS 帧,它就会将流状态从空闲转变为打开,然后半关闭,因此它与客户端匹配。 服务器半关闭状态意味着它可以发送任何帧,但只能接收 WINDOW_UPDATE、PRIORITY 或 RST_STREAM 帧。
对 GET 的响应包含消息内容,因此服务器发送 END_STREAM 标志设置为 0 的 HEADERS,然后发送 END_STREAM 标志设置为 1 的 DATA。DATA 帧触发服务器上流从半关闭到关闭的转换。 当客户端收到它时,它也会转换为关闭状态。 一旦流关闭,就无法发送或接收任何帧。
将此生命周期应用回并发上下文中,HTTP/2 指出:
处于“打开”状态或任一“半关闭”状态的流计入允许端点打开的最大流数。 处于这三种状态中任何一种状态的流都会计入 SETTINGS_MAX_CONCURRENT_STREAMS 设置中公布的限制。
理论上,并发限制是有用的。 然而,有一些实际因素阻碍了其有效性——我们将在本文后续介绍。
HTTP/2 请求取消
早些时候,我们讨论了客户取消传递中请求的情况。 HTTP/2 以比 HTTP/1.1 更有效的方式支持这一点。 客户端可以为单个流发送 RST_STREAM 帧,而不需要拆除整个连接。 这指示服务器停止处理请求并中止响应,从而释放服务器资源并避免浪费带宽。
让我们考虑一下之前的 3 个请求的示例。 这次,在发送所有标头后,客户端取消了流 1 上的请求。 服务器在准备好提供响应之前解析此 RST_STREAM 帧,并且仅响应流 3 和 5:
请求取消是一个有用的功能。 例如,当滚动包含多个图像的网页时,网络浏览器可以取消落在视口之外的图像,这意味着进入视口的图像可以更快地加载。 与 HTTP/1.1 相比,HTTP/2 使这种行为更加高效。
被取消的请求流会快速过渡整个流生命周期。 END_STREAM 标志设置为 1 的客户端 HEADERS 状态从空闲状态转换为打开状态再到半关闭状态,然后 RST_STREAM 立即导致从半关闭状态转换为关闭状态。
回想一下,只有处于打开或半关闭状态的流才会影响流并发限制。 当客户端取消流时,它立即能够在其位置打开另一个流,并可以立即发送另一个请求。 这是 CVE-2023-44487 发挥作用的关键。
快速重置导致拒绝服务
HTTP/2 请求取消可能被滥用来快速重置无限数量的流。 当 HTTP/2 服务器能够足够快地处理客户端发送的 RST_STREAM 帧并拆除状态时,这种快速重置不会导致问题。 当整理工作出现任何延误或滞后时,问题就会开始出现。 客户端可能会处理大量请求,从而导致工作积压,从而导致服务器上资源的过度消耗。
常见的 HTTP 部署架构是在其他组件之前运行 HTTP/2 代理或负载均衡器。 当客户端请求到达时,它会被快速分派,并且实际工作在其他地方作为异步活动完成。 这使得代理能够非常有效地处理客户端流量。 然而,这种关注点分离可能会使代理很难整理正在进行的作业。 因此,这些部署更有可能遇到快速重置带来的问题。
当 Cloudflare 的反向代理处理传入的 HTTP/2 客户端流量时,它们会将数据从连接的套接字复制到缓冲区中,并按顺序处理缓冲的数据。 当读取每个请求(标头和数据帧)时,它被分派到上游服务。 当读取 RST_STREAM 帧时,请求的本地状态将被拆除,并通知上游请求已被取消。 冲洗并重复,直到耗尽整个缓冲区。 然而,这种逻辑可能会被滥用:当恶意客户端开始发送大量请求链并在连接开始时重置时,我们的服务器会急切地读取所有请求,并对上游服务器造成压力,导致无法处理任何新传入的请求。
需要强调的重要一点是,流并发本身无法缓解快速重置。 无论服务器选择的 SETTINGS_MAX_CONCURRENT_STREAMS 值如何,客户端都可以搅动请求以创建高请求率。
快速重置剖析
以下是使用概念验证客户端尝试发出总共 1000 个请求的快速重置示例。 我使用了现成的服务器,没有任何缓解措施; 在测试环境中侦听端口 443。 为了清楚起见,使用 Wireshark 剖析流量并进行过滤以仅显示 HTTP/2 流量。 下载 pcap 以进行后续操作。
有点难看,因为有很多帧。 我们可以通过Wireshark的Statistics > HTTP2工具得到一个快速的总结:
此跟踪中的第一帧(数据包 14 中)是服务器的 SETTINGS 帧,它通告最大流并发数为 100。在数据包 15 中,客户端发送一些控制帧,然后开始发出快速重置的请求。 第一个 HEADERS 帧长 26 个字节,所有后续的 HEADERS 只有 9 个字节。 这种大小差异是由于称为 HPACK 的压缩技术造成的。 数据包 15 总共包含 525 个请求,直至流 1051。
有趣的是,流 1051 的 RST_STREAM 不适合数据包 15,因此在数据包 16 中我们看到服务器以 404 响应进行响应。 然后,在数据包 17 中,客户端发送 RST_STREAM,然后继续发送剩余的 475 个请求。
请注意,虽然服务器通告了 100 个并发流,但客户端发送的两个数据包发送的 HEADERS 帧比这多得多。 客户端不必等待服务器的任何返回流量,它仅受其可以发送的数据包大小的限制。 在此跟踪中没有看到服务器 RST_STREAM 帧,这表明服务器没有观察到并发流违规。
对客户的影响
如上所述,当请求被取消时,上游服务会收到通知,并可以在浪费太多资源之前中止请求。 这次攻击就是这种情况,大多数恶意请求从未转发到源服务器。 然而,这些攻击的规模确实造成了一些影响。
首先,随着传入请求的速率达到前所未有的峰值,我们收到了客户发现的 502 错误数量增加的报告。 这种情况发生在我们受影响最严重的数据中心,因为它们正在努力处理所有请求。让我们更深入地了解细节,重点关注传入请求到达我们的数据中心之一时如何处理:
我们可以看到我们的基础设施由一系列具有不同职责的不同代理服务器组成。 特别是,当客户端连接到 Cloudflare 发送 HTTPS 流量时,它首先会命中我们的 TLS 解密代理:它解密 TLS 流量,处理 HTTP 1、2 或 3 流量,然后将其转发到我们的“业务逻辑”代理。 它负责加载每个客户的所有设置,然后将请求正确路由到其他上游服务 - 更重要的是,在我们的例子中,它还负责安全功能。 这是处理 L7 攻击缓解的地方。
这种攻击媒介的问题在于它能够在每个连接中非常快速地发送大量请求。 在我们有机会阻止它们之前,它们中的每一个都必须转发到业务逻辑代理。 随着请求吞吐量变得高于我们的代理容量,连接这两个服务的管道在我们的一些服务器中达到了饱和水平。
发生这种情况时,TLS 代理无法再连接到其上游代理,这就是为什么某些客户端在最严重的攻击期间看到“502 Bad Gateway”错误的原因。 值得注意的是,截至今天,用于创建 HTTP 分析的日志也由我们的业务逻辑代理发出。 其结果是这些错误在 Cloudflare 仪表板中不可见。 我们的内部仪表板显示,大约 1% 的请求在第一波攻击期间(在我们实施缓解措施之前)受到影响,在 8 月 29 日最严重的攻击期间,峰值在 12% 左右,持续了几秒钟。 下图显示了发生这种情况时两个小时内这些错误的比率:
我们在接下来的几天里努力大幅减少这个数字,本文稍后将详细介绍。 由于我们堆栈的变化以及我们的缓解措施大大减少了这些攻击的规模,今天这个数字实际上为零:
499错误和HTTP/2流并发的挑战
一些客户报告的另一个症状是 499 错误增加。 其原因有点不同,与本文前面详细介绍的 HTTP/2 连接中的最大流并发有关。
HTTP/2 设置在连接开始时使用 SETTINGS 帧进行交换。 如果没有接收显式参数,则应用默认值。 一旦客户端建立了 HTTP/2 连接,它就可以等待服务器的设置(慢),也可以采用默认值并开始发出请求(快)。 对于 SETTINGS_MAX_CONCURRENT_STREAMS,默认值实际上是无限制的(流 ID 使用 31 位数字空间,请求使用奇数,因此实际限制为 1073741824)。 规范建议服务器提供不少于 100 个流。 客户端通常偏向于速度,因此不要等待服务器设置,这会产生一些竞争条件。 客户端正在对服务器可能选择的限制进行赌博; 如果他们选择错误,请求将被拒绝并必须重试。 在 1073741824 流上赌博有点愚蠢。 相反,许多客户端决定限制自己发出 100 个并发流,希望服务器遵循规范建议。 当服务器选择低于 100 的值时,客户端赌博就会失败并且流会被重置。
服务器重置流超出并发限制的原因有很多。 HTTP/2 是严格的,要求在出现解析或逻辑错误时关闭流。 2019 年,Cloudflare 开发了多种缓解措施来应对 HTTP/2 DoS 漏洞。 其中几个漏洞是由客户端行为不当引起的,导致服务器重置流。 遏制此类客户端的一个非常有效的策略是计算连接期间服务器重置的次数,当超过某个阈值时,使用 GOAWAY 帧关闭连接。 合法客户可能会在连接中犯一两个错误,这是可以接受的。 犯太多错误的客户端可能已损坏或恶意,关闭连接可以解决这两种情况。
在响应 CVE-2023-44487 启用的 DoS 攻击时,Cloudflare 将最大流并发数降低到 64。在进行此更改之前,我们没有意识到客户端不会等待 SETTINGS,而是假设并发数为 100。某些网页,例如 作为一个图片库,确实会导致浏览器在连接开始时立即发送 100 个请求。 不幸的是,超出限制的 36 个流都需要重置,这触发了我们的计数缓解措施。 这意味着我们关闭了合法客户端上的连接,导致页面加载完全失败。 当我们意识到这个互操作性问题后,我们将最大流并发数更改为 100。
Cloudflare措施和建议
2019 年,发现了多个与 HTTP/2 实现相关的 DoS 漏洞。 Cloudflare 开发并部署了一系列检测和缓解措施作为响应。 CVE-2023-44487是HTTP/2漏洞的另一种表现形式。 然而,为了缓解这一问题,我们能够扩展现有的保护措施来监视客户端发送的 RST_STREAM 帧,并在它们被滥用时关闭连接。 RST_STREAM 的合法客户端使用不受影响。
除了直接修复之外,我们还对服务器的 HTTP/2 帧处理和请求分派代码进行了多项改进。 此外,业务逻辑服务器还对排队和调度进行了改进,减少了不必要的工作并提高了取消响应能力。 这些共同减少了各种潜在滥用模式的影响,并为服务器在饱和之前提供了更多空间来处理请求。
尽早缓解攻击
Cloudflare 已经拥有适当的系统,可以通过更便宜的方法有效地缓解非常大的攻击。 其中之一被命名为“IP Jail”。 对于超容量攻击,该系统会收集参与攻击的客户端 IP,并阻止它们连接到受攻击的财产(无论是在 IP 级别还是在我们的 TLS 代理中)。 然而,该系统需要几秒钟才能完全生效; 在这宝贵的几秒钟内,源头已经受到保护,但我们的基础设施仍然需要吸收所有 HTTP 请求。 由于这种新的僵尸网络实际上没有启动期,因此我们需要能够在攻击成为问题之前将其消灭。
为了实现这一目标,我们扩展了 IP Jail 系统来保护我们的整个基础设施:一旦 IP 被“监禁”,不仅会阻止它连接到受攻击的财产,我们还会禁止相应的 IP 使用 HTTP/2 访问任何其他域 在 Cloudflare 上使用了一段时间。 由于使用 HTTP/1.x 不可能进行此类协议滥用,因此这限制了攻击者运行大型攻击的能力,而共享同一 IP 的任何合法客户端在此期间只会看到非常小的性能下降。 基于 IP 的缓解措施是一种非常生硬的工具——这就是为什么我们在大规模使用它们时必须非常小心,并尽可能避免误报。 此外,僵尸网络中给定 IP 的生命周期通常很短,因此任何长期缓解措施都可能弊大于利。 下图显示了我们目睹的攻击中 IP 的流失情况:
正如我们所看到的,在某一天发现的许多新 IP 随后很快就消失了。
由于所有这些操作都发生在 HTTPS 管道开始处的 TLS 代理中,因此与常规 L7 缓解系统相比,这可以节省大量资源。 这使我们能够更顺利地抵御这些攻击,现在由这些僵尸网络引起的随机 502 错误数量已降至零。
攻击可观测性改进
我们正在做出改变的另一个方面是可观察性。 在客户分析中不可见的情况下向客户返回错误是不令人满意的。 幸运的是,早在最近的攻击发生之前,一个对这些系统进行彻底检修的项目就已经开始进行。 最终,它将允许我们基础设施中的每个服务记录自己的数据,而不是依赖我们的业务逻辑代理来合并和发出日志数据。 这次事件凸显了这项工作的重要性,我们正在加倍努力。
我们还致力于更好的连接级日志记录,使我们能够更快地发现此类协议滥用,从而提高我们的 DDoS 缓解能力。
结论
虽然这是最新的破纪录攻击,但我们知道这不会是最后一次。 随着攻击变得越来越复杂,Cloudflare 不断努力主动识别新威胁 - 为我们的全球网络部署对策,以便我们数百万的客户立即得到自动保护。
自 2017 年以来,Cloudflare 一直为我们的所有客户提供免费、不计量且无限制的 DDoS 保护。此外,我们还提供一系列附加安全功能,以满足各种规模组织的需求。 如果您不确定自己是否受到保护或想了解如何受到保护。