abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · prometheus-unix-socket-dial
技术分析文章:https://abraxaslabs.tech/research/prometheus-unix-socket-dial
类别: SSRF 可达性: 远程
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
v3.15.0 新增了 Unix 域套接字抓取功能(#12024 / #18091)。该路径来自目标标签 __unix_socket__,而非运维人员配置的允许列表。http_sd 和 file_sd 会将该标签原样复制。PopulateLabels 从不校验它。随后 newUnixSocketScrapeClient 调用 DialContext(ctx, "unix", socketPath),既没有 filepath.Clean,也没有允许列表,也不拒绝 ../ 或 Linux 抽象 @ 名称。
TCP 抓取无法做到这一点。 CheckTargetAddress 仍会丢弃包含 / 的 __address__ 值,因此将 unix:///var/run/docker.sock 作为地址会被拒绝。新增的标签才是漏洞所在。
默认配置不会设置该标签。对 Prometheus 的未认证 HTTP 请求无法添加作业。此漏洞受 SD 门控。
| ID | 暂无 CVE |
| 类别 | SSRF(来自 SD 标签的未净化 unix Dial) |
| 可达性 | 远程(受 SD 门控:http_sd / file_sd 的写入者) |
| CWE | CWE-918、CWE-22 |
| CVSS | 高危:8.6 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| 产品 | Prometheus |
| 受影响版本 | v3.15.0 (5241a27fe3c6983549fccc32f6e65917408c63cd),镜像 prom/prometheus:v3.15.0 |
| 认证 | 受 SD 门控:http_sd / file_sd 标签的写入者(或将不可信元数据复制到 __unix_socket__ 的重标记配置) |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
控制 http_sd JSON(或可写的 file_sd)就足够了。SD URL 属于运维配置。遗留问题在于,组 labels 可以设置 __unix_socket__,而 Prometheus 会对其发起 Dial。
| 权限 | 方法 / 汇聚点 | 实验验证或推断 |
|---|---|---|
| 连接到 Prometheus UID 可打开的宿主机 unix socket | 从 __unix_socket__ 调用 clientForTarget -> DialContext(ctx, "unix", socketPath) | 实验验证。来自 http_sd 的路径 /sockets/jail/../oracle.sock;抓取健康状态为 up |
在该路径中跟随 ../ | 没有 filepath.Clean / 没有拒绝 .. | 实验验证。内核将路径遍历解析为 /sockets/oracle.sock |
| 拨号 Linux 抽象 socket | 对 @name 执行相同的 Dial | 实验验证。__unix_socket__=@prom-lab-oracle;抓取健康状态为 up |
| 以 Prometheus UID 在该 socket 上发起 HTTP GET | 抓取为 GET;__metrics_path__ 即请求路径 | 实验验证。GET /fs-traversal 和 /abstract;User-Agent 为 Prometheus/;lab_witness 被摄取 |
| 访问 TCP 抓取无法命名的 socket | CheckTargetAddress 拒绝 __address__ 中的 / | 实验验证。将 unix:///var/run/docker.sock 作为 __address__ 时记录 is not a valid hostname;不是活动目标;prometheus_target_sync_failed_total=1 |
| 若这些 socket 在范围内,则 GET Docker Engine / kubelet HTTP API | __unix_socket__=/var/run/docker.sock 加上 Docker API 路径 | 推断。相同的未净化 Dial。本实验未挂载真实引擎 socket。JSON API 通常无法通过 exposition 解析,因此高危点是连接/GET,而非对 Docker JSON 的 TSDB 数据外泄 |
| 修改 Docker(创建容器、写入) | 抓取动词 | 已排除。抓取仅限 GET |
| 通过未认证的 Prometheus HTTP 添加抓取任务 | /-/reload、admin API | 已排除。两个标志默认关闭;reload 没有请求体 |
| 在默认示例配置上使用此方法 | __unix_socket__ 未设置;示例通过 TCP 抓取 localhost:9090 | 已排除。树内 k8s/consul/docker SD 仅发出 __meta_*;这些前缀在 relabel 后被删除 |
厂商安全模型已允许 SD 选择 TCP 抓取目标。新的原语是 __address__ 无法命名的宿主机 IPC。
在 3.15 的 UDS 抓取 FEATURE 行之后,我固定了 prometheus/prometheus v3.15.0。此固定版本上的默认 OSS 关闭了 admin API、lifecycle HTTP、remote-write receiver 和 OTLP receiver。PopulateDiscoveredLabels 会复制每个 http_sd 组标签。__unix_socket__ 不是 __meta_*,因此它在元数据清除后仍然保留。PopulateLabels 仅检查 __address__。
func (sp *scrapePool) clientForTarget(t *Target) *http.Client {
socketPath := t.labels.Get(UnixSocketLabel)
if socketPath == "" {
return sp.client
}
client, err := newUnixSocketScrapeClient(socketPath, ...)
func newUnixSocketScrapeClient(socketPath string, ...) (*http.Client, error) {
optFuncs = append([]config_util.HTTPClientOption{config_util.WithDialContextFunc(
func(ctx context.Context, _, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
},
)}, optFuncs...)
CheckTargetAddress 仍然会拒绝 address 中的 /。URL() 会从 __scheme__ / __address__ / __metrics_path__ 保留 scheme/host/path;socket 仅用于拨号。
已记录的误判:将其评为模型内 NONE,因为 SD 已经选择 TCP 目标(新原语是主机 IPC);将 unix:///var/run/docker.sock 作为 __address__ 视为漏洞(它会被拒绝);声称默认示例配置就是这种遗留问题。表演:反向 shell。判定标准是 SD JSON 所命名 socket 上的抓取健康状态 up。
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
或
cd lab
./run.sh
prom/prometheus:v3.15.0 与一个 Python sidecar 共享同一个 netns。该 sidecar 在 127.0.0.1:8080/targets.json 提供 http_sd,并提供两个 unix HTTP oracle:文件系统 /sockets/oracle.sock 和抽象 @prom-lab-oracle。Prometheus 发布在 127.0.0.1:19390。Admin API 和生命周期保持关闭。
SUCCESS prometheus-unix-socket-dial sd=http_sd fs-traversal=yes abstract=yes tcp-slash-rejected=yes how=sd-labels PROMETHEUS-UNIX-SOCKET-DIAL-WITNESS
IOC:scrapeUrl=http://localhost:1/fs-traversal 和 /abstract,两者的健康状态均为 up。TCP slash 从未成为活动目标。
在 __unix_socket__ 成为经过清理的绝对文件系统路径之前,不要从 SD 中 Dial 它。拒绝空值、NUL、Linux 抽象 @ 名称、相对路径,以及 filepath.Clean 之后仍存在的任何 ..。来自 scrape YAML 的前缀允许列表更为可靠。除非标签来自操作员配置,否则从 PopulateDiscoveredLabels 中丢弃该标签,也能封堵 SD 注入。
5241a27fe3c6983549fccc32f6e65917408c63cd)scrape/scrape.go · scrape/target.go · config/config.goGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。