
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
Write-up: https://abraxaslabs.tech/research/prometheus-unix-socket-dial
Klasse: SSRF Reichweite: Remote
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
v3.15.0 führte Unix Domain Socket Scraping ein (#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 anschließend DialContext(ctx, "unix", socketPath) auf, ohne filepath.Clean, ohne Allowlist und ohne Ablehnung von ../ oder Linux-Abstract-Namen mit @.
TCP-Scraping kann das nicht. CheckTargetAddress verwirft weiterhin __address__-Werte, die / enthalten, sodass unix:///var/run/docker.sock als Adresse abgelehnt wird. Das neue Label ist die Lücke.
Die Standardkonfiguration setzt das Label nicht. Unauth HTTP zu 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 Metadaten in __unix_socket__ kopiert) |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Die Kontrolle über http_sd-JSON (oder beschreibbares file_sd) genügt. Die SD-URL ist Operator-Konfiguration. Der verbleibende Punkt ist, dass Gruppen-labels __unix_socket__ setzen können und Prometheus dieses dann per Dial anspricht.
| Privileg | Methode / Sink | Im Labor nachgewiesen oder abgeleitet |
|---|---|---|
| Verbindung zu einem Host-Unix-Socket, den die Prometheus-UID öffnen kann | clientForTarget -> DialContext(ctx, "unix", socketPath) aus __unix_socket__ | Im Labor nachgewiesen. Pfad /sockets/jail/../oracle.sock aus http_sd; Scrape-Health up |
../ in diesem Pfad folgen | kein filepath.Clean / keine ..-Ablehnung | Im Labor nachgewiesen. Der Kernel löste den Traversal zu /sockets/oracle.sock auf |
| Ein Linux-Abstract-Socket dialen | derselbe Dial von @name | Im Labor nachgewiesen. __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 | Im Labor nachgewiesen. GET /fs-traversal und /abstract; User-Agent Prometheus/; lab_witness ingestiert |
| Sockets erreichen, die TCP-Scrape nicht benennen kann | CheckTargetAddress lehnt / in __address__ ab | Im Labor nachgewiesen. 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, falls diese Sockets im Scope liegen | __unix_socket__=/var/run/docker.sock plus ein Docker-API-Pfad | Abgeleitet. Derselbe unsanitisierte Dial. Dieses Labor mountet keinen echten Engine-Socket. JSON-APIs scheitern üblicherweise an der Exposition-Parse, daher ist das High der Connect/GET, nicht TSDB-Exfil von Docker-JSON |
| Docker mutieren (Container erstellen, schreiben) | Scrape-Verben | Geschlossen. Scrape ist GET-only |
| Scrape-Jobs über unauthentifiziertes 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 Relabel gelöscht |
Das Sicherheitsmodell des Herstellers erlaubt SD bereits, TCP-Scrape-Targets zu wählen. Das neue Primitiv ist Host-IPC, die __address__ nicht benennen konnte.
Ich habe prometheus/prometheus v3.15.0 nach der 3.15-FEATURE-Zeile für UDS-Scrape gepinnt. Das Standard-OSS auf diesem Pin hat Admin-API, Lifecycle-HTTP, Remote-Write-Receiver und OTLP-Receiver aus. PopulateDiscoveredLabels kopiert jedes http_sd-Gruppenlabel. __unix_socket__ ist nicht __meta_*, daher übersteht es 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 Schema/Host/Pfad aus __scheme__ / __address__ / __metrics_path__; der Socket dient nur zum Wählen.
Bereits festgehaltene Fehlannahmen: dies als In-Modell-NONE zu bewerten, weil SD bereits TCP-Ziele auswählt (das neue Primitiv ist Host-IPC); unix:///var/run/docker.sock als __address__ als die Lücke zu behandeln (es wird abgelehnt); zu behaupten, die Standard-Beispielkonfiguration sei dieser Überrest. Theater: eine Reverse Shell. Das Orakel ist die Scrape-Gesundheit 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 stellt http_sd unter 127.0.0.1:8080/targets.json und zwei Unix-HTTP-Orakel bereit: Dateisystem /sockets/oracle.sock und abstrakt @prom-lab-oracle. Prometheus wird unter 127.0.0.1:19390 veröffentlicht. Admin-API und Lifecycle bleiben deaktiviert.
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-Status up. Der TCP-Slash wurde nie zu einem aktiven Target.
Rufe __unix_socket__ nicht von SD aus per Dial auf, solange es kein bereinigter absoluter Dateisystempfad ist. Lehne leere Werte, NUL, Linux-Abstract-@-Namen, relative Pfade und jedes .. nach filepath.Clean ab. Eine Allowlist von Präfixen aus der Scrape-YAML ist stärker. Auch das Entfernen des Labels aus PopulateDiscoveredLabels, sofern es nicht aus der Operator-Konfiguration stammt, schließt die SD-Injection.