
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
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__, pas 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 ../ ni des noms abstraits Linux @.
Le scrape TCP ne peut pas faire cela. CheckTargetAddress rejette toujours les valeurs __address__ contenant /, donc unix:///var/run/docker.sock en tant qu'adresse est rejeté. Le nouveau label est la faille.
La configuration par défaut ne définit pas le label. Une requête HTTP non authentifiée vers Prometheus ne peut pas ajouter de jobs. C'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 accessible en écriture) suffit. L'URL du SD relève de la configuration de l'opérateur. Le reste, c'est que les labels du groupe peuvent définir __unix_socket__ et que Prometheus effectuera le Dial.
| Privilège | Méthode / sink | Prouvé en lab ou déduit |
|---|---|---|
| Se connecter à un socket unix de l'hôte que l'UID Prometheus peut ouvrir | clientForTarget -> DialContext(ctx, "unix", socketPath) depuis __unix_socket__ | Prouvé en lab. 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 lab. Le noyau a résolu la traversée vers /sockets/oracle.sock |
| Dialer un socket abstrait Linux | même Dial de @name | Prouvé en lab. __unix_socket__=@prom-lab-oracle ; santé du scrape up |
| Requête 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 lab. 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 lab. 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 lab ne monte pas de vrai socket de moteur. Les API JSON échouent généralement à l'analyse d'exposition, donc le score Élevé concerne la connexion/le GET, pas l'exfiltration TSDB du JSON Docker |
| Modifier Docker (créer un conteneur, écrire) | verbes du scrape | Fermé. Le scrape est en GET uniquement |
| Ajouter des jobs de scrape via 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 les 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 3.15 pour le scrape UDS. L'OSS par défaut sur ce pin a l'API admin, le lifecycle HTTP, le récepteur remote-write et le récepteur OTLP désactivés. PopulateDiscoveredLabels recopie chaque label de groupe http_sd. __unix_socket__ n'est pas __meta_*, donc il survit à l'effacement des métadonnées. 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 uniquement utilisé pour le Dial.
Fausses pistes déjà consignées : noter cela comme NONE dans le modèle parce que le SD choisit déjà les cibles TCP (la nouvelle primitive est l'IPC hôte) ; considérer unix:///var/run/docker.sock comme __address__ comme la faille (il est rejeté) ; prétendre que la config d'exemple par défaut est ce reste. 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 sur 127.0.0.1:8080/targets.json et deux oracles HTTP unix : système de fichiers /sockets/oracle.sock et abstrait @prom-lab-oracle. Prometheus est publié sur 127.0.0.1:19390. L'API admin et le lifecycle 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 en santé up. Le slash TCP n'est jamais devenu une cible active.
Ne pas Dialer __unix_socket__ depuis le SD tant qu'il n'est pas un chemin de système de fichiers absolu assaini. Rejeter les valeurs vides, le NUL, les noms abstraits Linux @, les chemins relatifs et tout .. après filepath.Clean. Une liste d'autorisation de préfixes issue du YAML de scrape est plus robuste. Supprimer le label de PopulateDiscoveredLabels sauf s'il provient de la configuration de l'opérateur ferme également l'injection SD.