
Prometheus v3.15.0 – Hoch – Remote – SSRF – http_sd __unix_socket__ unsanitisierter Dial-Rest des UDS-Scrape
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · prometheus-unix-socket-dial
Klasse: SSRF Reichweite: Remote
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
v3.15.0 fügte Unix Domain Socket Scrape hinzu (#12024 / #18091). Der Pfad ist ein Target-Label __unix_socket__, keine Operator-Allowlist. http_sd und file_sd kopieren dieses Label unverändert durch. PopulateLabels validiert es nie. newUnixSocketScrapeClient ruft dann DialContext(ctx, "unix", socketPath) auf, ohne filepath.Clean, ohne Allowlist und ohne Ablehnung von ../ oder Linux-Abstract-@-Namen.
TCP-Scrape kann das nicht. CheckTargetAddress verwirft weiterhin __address__-Werte, die / enthalten, sodass unix:///var/run/docker.sock als Adresse abgelehnt wird. Das neue Label ist das Loch.
Die Standardkonfiguration setzt das Label nicht. Unauth HTTP an Prometheus kann keine Jobs hinzufügen. Dies ist SD-gated.
| ID | noch keine CVE |
| Klasse | SSRF (unsanitized unix Dial aus SD-Labels) |
| Reichweite | Remote (SD-gated: Schreiber von 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 |
| Produkt | Prometheus |
| Betroffen | v3.15.0 (5241a27fe3c6983549fccc32f6e65917408c63cd), Image prom/prometheus:v3.15.0 |
| Auth | SD-gated: Schreiber von http_sd / file_sd-Labels (oder ein Relabel, das nicht vertrauenswürdige Meta in __unix_socket__ kopiert) |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Kontrolle über http_sd-JSON (oder beschreibbares file_sd) genügt. Die SD-URL ist Operator-Konfiguration. Der Rest ist, dass Gruppen-labels __unix_socket__ setzen können und Prometheus es dann anwählt.
| Privileg | Methode / Sink | Lab-proven oder abgeleitet |
|---|---|---|
| Verbindung zu einem Host-Unix-Socket, den die Prometheus-UID öffnen kann | clientForTarget -> DialContext(ctx, "unix", socketPath) aus __unix_socket__ | Lab-proven. Pfad /sockets/jail/../oracle.sock aus http_sd; Scrape-Health up |
../ in diesem Pfad folgen | kein filepath.Clean / keine ..-Ablehnung | Lab-proven. Kernel löste den Traversal zu /sockets/oracle.sock auf |
| Einen Linux-Abstract-Socket anwählen | derselbe Dial von @name | Lab-proven. __unix_socket__=@prom-lab-oracle; Scrape-Health up |
| HTTP GET auf diesem Socket als die Prometheus-UID | Scrape ist GET; __metrics_path__ ist der Request-Pfad | Lab-proven. GET /fs-traversal und /abstract; User-Agent Prometheus/; lab_witness ingestiert |
| Sockets erreichen, die TCP-Scrape nicht benennen kann | CheckTargetAddress lehnt / in __address__ ab | Lab-proven. unix:///var/run/docker.sock als __address__ protokollierte is not a valid hostname; kein aktives Target; prometheus_target_sync_failed_total=1 |
| GET auf Docker Engine / kubelet HTTP APIs, wenn diese Sockets im Scope sind | __unix_socket__=/var/run/docker.sock plus ein Docker-API-Pfad | Abgeleitet. Derselbe unsanitized Dial. Dieses Lab mountet keinen echten Engine-Socket. JSON-APIs scheitern meist an der Exposition-Parse, daher ist das High die Connect/GET, nicht TSDB-Exfil von Docker-JSON |
| Docker mutieren (Container erstellen, schreiben) | Scrape-Verben | Geschlossen. Scrape ist GET-only |
| Scrape-Jobs über unauth Prometheus HTTP hinzufügen | /-/reload, Admin-API | Geschlossen. Beide Flags standardmäßig aus; Reload hat keinen Body |
| Dies auf der Standard-Beispielkonfiguration verwenden | __unix_socket__ nicht gesetzt; Beispiel scrapt localhost:9090 über TCP | Geschlossen. In-tree k8s/consul/docker SD emittieren nur __meta_*; diese Präfixe werden nach dem Relabel gelöscht |
Das Vendor-Sicherheitsmodell erlaubt SD bereits, TCP-Scrape-Targets zu wählen. Das neue Primitiv ist Host-IPC, das __address__ nicht benennen konnte.
Ich pinnte prometheus/prometheus v3.15.0 nach der 3.15-FEATURE-Linie für UDS-Scrape. Standard-OSS auf diesem Pin hat Admin-API, Lifecycle-HTTP, Remote-Write-Receiver und OTLP-Receiver aus. PopulateDiscoveredLabels kopiert jedes http_sd-Gruppen-Label. __unix_socket__ ist nicht __meta_*, überlebt also den Meta-Wipe. PopulateLabels prüft nur __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 lehnt weiterhin / in der Adresse ab. URL() behält Scheme/Host/Pfad aus __scheme__ / __address__ / __metrics_path__; der Socket ist dial-only.
Bereits festgehaltene Irrwege: dies als in-model NONE zu bewerten, weil SD bereits TCP-Targets wählt (das neue Primitiv ist Host-IPC); unix:///var/run/docker.sock als __address__ als das Loch zu behandeln (es wird abgelehnt); zu behaupten, die Standard-Beispielkonfiguration sei dieser Rest. Theater: eine Reverse Shell. Das Orakel ist Scrape-Health up auf Sockets, die das SD-JSON benannt hat.
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
oder
cd lab
./run.sh
prom/prometheus:v3.15.0 teilt sich einen Netns mit einem Python-Sidecar. Der Sidecar bedient http_sd unter 127.0.0.1:8080/targets.json und zwei Unix-HTTP-Orakel: Dateisystem /sockets/oracle.sock und Abstract @prom-lab-oracle. Prometheus wird unter 127.0.0.1:19390 veröffentlicht. Admin-API und Lifecycle bleiben aus.
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 und /abstract, beide Health up. TCP-Slash wurde nie ein aktives Target.
__unix_socket__ nicht aus SD anwählen, bis es ein sanitisierter absoluter Dateisystempfad ist. Leere Werte, NUL, Linux-Abstract-@-Namen, relative Pfade und jedes .. nach filepath.Clean ablehnen. Eine Allowlist von Präfixen aus Scrape-YAML ist stärker. Das Label aus PopulateDiscoveredLabels zu entfernen, sofern es nicht aus der Operator-Konfiguration stammt, schließt ebenfalls die SD-Injektion.