
Prometheus v3.15.0 - 高 - リモート - SSRF - http_sd __unix_socket__ 未サニタイズの Dial が UDS スクレイプに残存
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 Domain Socket スクレイプが追加されました(#12024 / #18091)。そのパスはターゲットラベル __unix_socket__ であり、オペレーターの許可リストではありません。http_sd と file_sd はそのラベルをそのままコピーします。PopulateLabels はそれを一切検証しません。そして newUnixSocketScrapeClient は filepath.Clean も許可リストもなく、../ や Linux の抽象名前空間 @ 名を拒否することもなく、DialContext(ctx, "unix", socketPath) を呼び出します。
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 | High: 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__ にコピーする relabel) |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
http_sd JSON(または書き込み可能な file_sd)を制御できれば十分です。SD URL はオペレーターの設定です。残る問題は、グループの labels が __unix_socket__ を設定でき、Prometheus がそれに対して Dial を実行してしまうことです。
| 権限 | 手法 / シンク | ラボ実証または推測 |
|---|---|---|
| Prometheus UID が開けるホストの unix ソケットに接続する | __unix_socket__ から clientForTarget -> DialContext(ctx, "unix", socketPath) | ラボ実証済み。http_sd からのパス /sockets/jail/../oracle.sock、スクレイプヘルス up |
そのパス内の ../ をたどる | filepath.Clean なし / .. 拒否なし | ラボ実証済み。カーネルがトラバーサルを /sockets/oracle.sock に解決した |
| Linux 抽象ソケットにダイヤルする | @name の同じ Dial | ラボ実証済み。__unix_socket__=@prom-lab-oracle、スクレイプヘルス up |
| Prometheus UID としてそのソケット上で HTTP GET を実行する | スクレイプは GET、__metrics_path__ がリクエストパス | ラボ実証済み。GET /fs-traversal と /abstract、User-Agent Prometheus/、lab_witness が取り込まれた |
| TCP スクレイプでは指定できないソケットに到達する | CheckTargetAddress が __address__ 内の / を拒否する | ラボ実証済み。__address__ としての unix:///var/run/docker.sock は is not a valid hostname とログ出力され、アクティブなターゲットにはならず、prometheus_target_sync_failed_total=1 |
| それらのソケットがスコープ内にある場合に Docker Engine / kubelet HTTP API を GET する | __unix_socket__=/var/run/docker.sock と Docker API パス | 推測。同じサニタイズされていない Dial。このラボでは実際のエンジンソケットをマウントしていない。JSON API は通常エクスポジションのパースに失敗するため、High は接続/GET であり、Docker JSON の TSDB への持ち出しではない |
| Docker を変更する(コンテナ作成、書き込み) | スクレイプの動詞 | クローズ。スクレイプは GET のみ |
| 認証なしの Prometheus HTTP 経由でスクレイプジョブを追加する | /-/reload、admin API | クローズ。両フラグともデフォルトでオフ、reload にはボディがない |
| デフォルトのサンプル設定でこれを使う | __unix_socket__ は未設定、サンプルは localhost:9090 を TCP 経由でスクレイプ | クローズ。ツリー内の k8s/consul/docker SD は __meta_* のみを出力し、それらのプレフィックスは relabel 後に削除される |
ベンダーのセキュリティモデルはすでに SD が TCP スクレイプターゲットを選択することを許可している。新しいプリミティブは、__address__ では指定できなかったホスト IPC である。
UDS スクレイプの 3.15 FEATURE ラインの後、prometheus/prometheus v3.15.0 をピン留めした。このピンでのデフォルト OSS は、admin API、ライフサイクル HTTP、remote-write レシーバー、OTLP レシーバーがオフになっている。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 を保持し、ソケットは dial 専用です。
すでに記録済みの誤った方向性: SD がすでに TCP ターゲットを選択しているため、これをモデル内の NONE としてスコアリングする(新しいプリミティブはホスト IPC); unix:///var/run/docker.sock を __address__ として扱うことを穴とみなす(これは拒否される); デフォルトのサンプル設定がこの残存物であると主張する。劇場: リバースシェル。オラクルは、SD JSON が指定したソケット上でのスクレイプ健全性 up です。
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
または
cd lab
./run.sh
prom/prometheus:v3.15.0 は Python サイドカーと netns を共有します。サイドカーは 127.0.0.1:8080/targets.json で http_sd を提供し、2 つの unix HTTP オラクルを提供します。ファイルシステム /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 スラッシュはアクティブなターゲットにはならなかった。
サニタイズ済みの絶対ファイルシステムパスになるまで、SD から __unix_socket__ を Dial しないこと。空の値、NUL、Linux の抽象 @ 名、相対パス、および filepath.Clean 後の .. を拒否する。scrape YAML からのプレフィックスの許可リストの方がより強固である。オペレーター設定に由来しない限り PopulateDiscoveredLabels からラベルを削除することも、SD インジェクションを塞ぐ。
5241a27fe3c6983549fccc32f6e65917408c63cd)scrape/scrape.go · scrape/target.go · config/config.goGNU Affero GPL v3.0。LICENSE を参照。ループバックラボ専用。保証なし。