
Prometheus v3.15.0 - Élevé - À distance - SSRF - http_sd __unix_socket__ Dial non assaini résiduel du scrape UDS
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · prometheus-unix-socket-dial
Write-up : https://abraxaslabs.tech/research/prometheus-unix-socket-dial
Classe : SSRF Portée : Distante
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
La v3.15.0 a ajouté le scrape via Unix Domain Socket (#12024 / #18091). Le chemin est un label de cible __unix_socket__, et non une liste d'autorisation d'opérateur. http_sd et file_sd recopient ce label tel quel. PopulateLabels ne le valide jamais. newUnixSocketScrapeClient appelle ensuite DialContext(ctx, "unix", socketPath) sans filepath.Clean, sans liste d'autorisation et sans rejet des noms ../ ou des noms abstraits Linux @.
Le scrape TCP ne permet pas cela. CheckTargetAddress rejette toujours les valeurs __address__ contenant /, donc unix:///var/run/docker.sock en tant qu'adresse est rejeté. Le nouveau label constitue la faille.
La configuration par défaut ne définit pas le label. Un accès HTTP non authentifié à Prometheus ne permet pas d'ajouter des jobs. Cela est conditionné par le SD.
| ID | pas encore de CVE |
| Classe | SSRF (Dial unix non assaini depuis les labels SD) |
| Portée | Distante (conditionnée par le SD : auteur de http_sd / file_sd) |
| CWE | CWE-918, CWE-22 |
| CVSS | Élevé : 8.6 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| Produit | Prometheus |
| Affecté | v3.15.0 (5241a27fe3c6983549fccc32f6e65917408c63cd), image prom/prometheus:v3.15.0 |
| Auth | Conditionnée par le SD : auteur des labels http_sd / file_sd (ou un relabel qui recopie des métadonnées non fiables dans __unix_socket__) |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Le contrôle du JSON http_sd (ou d'un file_sd inscriptible) suffit. L'URL SD relève de la configuration de l'opérateur. Le point restant est que les labels du groupe peuvent définir __unix_socket__ et que Prometheus effectuera un Dial dessus.
| Privilège | Méthode / sink | Prouvé en labo ou déduit |
|---|---|---|
| Se connecter à un socket unix hôte que l'UID Prometheus peut ouvrir | clientForTarget -> DialContext(ctx, "unix", socketPath) depuis __unix_socket__ | Prouvé en labo. Chemin /sockets/jail/../oracle.sock depuis http_sd ; santé du scrape up |
Suivre ../ dans ce chemin | pas de filepath.Clean / pas de rejet de .. | Prouvé en labo. Le noyau a résolu la traversée vers /sockets/oracle.sock |
| Se connecter à un socket abstrait Linux | même Dial de @name | Prouvé en labo. __unix_socket__=@prom-lab-oracle ; santé du scrape up |
| HTTP GET sur ce socket en tant qu'UID Prometheus | le scrape est un GET ; __metrics_path__ est le chemin de la requête | Prouvé en labo. GET /fs-traversal et /abstract ; User-Agent Prometheus/ ; lab_witness ingéré |
| Atteindre des sockets que le scrape TCP ne peut pas nommer | CheckTargetAddress rejette / dans __address__ | Prouvé en labo. unix:///var/run/docker.sock en tant que __address__ a journalisé is not a valid hostname ; pas une cible active ; prometheus_target_sync_failed_total=1 |
| GET sur les API HTTP de Docker Engine / kubelet si ces sockets sont dans le périmètre | __unix_socket__=/var/run/docker.sock plus un chemin d'API Docker | Déduit. Même Dial non assaini. Ce labo ne monte pas de vrai socket de moteur. Les API JSON échouent généralement à l'analyse d'exposition, donc le niveau High concerne la connexion/le GET, pas l'exfiltration TSDB du JSON Docker |
| Modifier Docker (créer un conteneur, écrire) | verbes de scrape | Fermé. Le scrape est en GET uniquement |
| Ajouter des jobs de scrape via l'HTTP Prometheus non authentifié | /-/reload, API admin | Fermé. Les deux flags sont désactivés par défaut ; reload n'a pas de corps |
| Utiliser ceci sur la config d'exemple par défaut | __unix_socket__ non défini ; l'exemple scrape localhost:9090 en TCP | Fermé. Les SD k8s/consul/docker intégrés n'émettent que des __meta_* ; ces préfixes sont supprimés après relabel |
Le modèle de sécurité du fournisseur permet déjà au SD de choisir des cibles de scrape TCP. La nouvelle primitive est l'IPC hôte que __address__ ne pouvait pas nommer.
J'ai épinglé prometheus/prometheus v3.15.0 après la ligne FEATURE de la 3.15 pour le scrape UDS. L'OSS par défaut sur cet épinglage a l'API admin, le lifecycle HTTP, le récepteur remote-write et le récepteur OTLP désactivés. PopulateDiscoveredLabels copie chaque label de groupe http_sd. __unix_socket__ n'est pas __meta_*, donc il survit à l'effacement des meta. PopulateLabels ne vérifie que __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 rejette toujours / dans l'adresse. URL() conserve scheme/host/path depuis __scheme__ / __address__ / __metrics_path__ ; le socket est en dial-only.
Fausses pistes déjà consignées : noter cela comme NONE dans le modèle parce que SD choisit déjà des cibles TCP (la nouvelle primitive est l'IPC hôte) ; traiter unix:///var/run/docker.sock comme __address__ comme la faille (il est rejeté) ; affirmer que la config d'exemple par défaut est ce reliquat. Théâtre : un reverse shell. L'oracle est la santé du scrape up sur les sockets nommés par le JSON SD.
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
ou
cd lab
./run.sh
prom/prometheus:v3.15.0 partage un netns avec un sidecar Python. Le sidecar sert http_sd à l'adresse 127.0.0.1:8080/targets.json et deux oracles HTTP unix : le système de fichiers /sockets/oracle.sock et l'abstrait @prom-lab-oracle. Prometheus est publié à l'adresse 127.0.0.1:19390. L'API d'administration et le cycle de vie restent désactivés.
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 et /abstract, tous deux avec l'état de santé up. Le slash TCP n'est jamais devenu une cible active.