
Prometheus v3.15.0 - Alta - Remota - SSRF - http_sd __unix_socket__ Dial sin sanear remanente de scrape UDS
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · prometheus-unix-socket-dial
Clase: SSRF Alcance: Remoto
Prometheus v3.15.0 (5241a27) - CNCF / Prometheus
v3.15.0 añadió el scrape de Unix Domain Socket (#12024 / #18091). La ruta es una etiqueta de destino __unix_socket__, no una lista de permitidos del operador. http_sd y file_sd copian esa etiqueta tal cual. PopulateLabels nunca la valida. newUnixSocketScrapeClient luego llama a DialContext(ctx, "unix", socketPath) sin filepath.Clean, sin lista de permitidos y sin rechazar ../ ni nombres abstractos de Linux @.
El scrape TCP no puede hacer esto. CheckTargetAddress todavía descarta los valores de __address__ que contienen /, por lo que unix:///var/run/docker.sock como dirección es rechazado. La nueva etiqueta es el agujero.
La configuración estándar no establece la etiqueta. HTTP sin autenticación hacia Prometheus no puede añadir jobs. Esto está condicionado por SD.
| ID | aún sin CVE |
| Clase | SSRF (Dial unix sin sanear desde etiquetas SD) |
| Alcance | Remoto (condicionado por SD: escritor de http_sd / file_sd) |
| CWE | CWE-918, CWE-22 |
| CVSS | Alto: 8.6 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| Producto | Prometheus |
| Afectado | v3.15.0 (5241a27fe3c6983549fccc32f6e65917408c63cd), imagen prom/prometheus:v3.15.0 |
| Autenticación | Condicionado por SD: escritor de etiquetas http_sd / file_sd (o un relabel que copie meta no confiable en __unix_socket__) |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
El control del JSON de http_sd (o de un file_sd escribible) es suficiente. La URL de SD es configuración del operador. Lo que queda es que las labels del grupo pueden establecer __unix_socket__ y Prometheus hará Dial sobre ella.
| Privilegio | Método / sink | Probado en laboratorio o inferido |
|---|---|---|
| Conectar a un unix socket del host que el UID de Prometheus pueda abrir | clientForTarget -> DialContext(ctx, "unix", socketPath) desde __unix_socket__ | Probado en laboratorio. Ruta /sockets/jail/../oracle.sock desde http_sd; salud del scrape up |
Seguir ../ en esa ruta | sin filepath.Clean / sin rechazo de .. | Probado en laboratorio. El kernel resolvió el traversal a /sockets/oracle.sock |
| Hacer Dial a un socket abstracto de Linux | mismo Dial de @name | Probado en laboratorio. __unix_socket__=@prom-lab-oracle; salud del scrape up |
| HTTP GET sobre ese socket como el UID de Prometheus | el scrape es GET; __metrics_path__ es la ruta de la petición | Probado en laboratorio. GET /fs-traversal y /abstract; User-Agent Prometheus/; lab_witness ingerido |
| Alcanzar sockets que el scrape TCP no puede nombrar | CheckTargetAddress rechaza / en __address__ | Probado en laboratorio. unix:///var/run/docker.sock como __address__ registró is not a valid hostname; no es un target activo; prometheus_target_sync_failed_total=1 |
| GET a las APIs HTTP de Docker Engine / kubelet si esos sockets están en alcance | __unix_socket__=/var/run/docker.sock más una ruta de la API de Docker | Inferido. Mismo Dial sin sanear. Este laboratorio no monta un socket real del engine. Las APIs JSON normalmente fallan el parseo de exposición, así que el Alto es el connect/GET, no la exfiltración de TSDB de JSON de Docker |
| Mutar Docker (crear contenedor, escribir) | verbos del scrape | Cerrado. El scrape es solo GET |
| Añadir jobs de scrape por HTTP sin autenticación a Prometheus | /-/reload, API de admin | Cerrado. Ambos flags están desactivados por defecto; reload no tiene body |
| Usar esto en la configuración de ejemplo por defecto | __unix_socket__ sin establecer; el ejemplo scrapea localhost:9090 por TCP | Cerrado. Los SD k8s/consul/docker del árbol solo emiten __meta_*; esos prefijos se eliminan tras el relabel |
El modelo de seguridad del proveedor ya permite que SD elija targets de scrape TCP. La nueva primitiva es IPC del host que __address__ no podía nombrar.
Fijé prometheus/prometheus v3.15.0 tras la línea FEATURE de 3.15 para el scrape UDS. El OSS por defecto en este pin tiene la API de admin, el HTTP de lifecycle, el receptor de remote-write y el receptor OTLP desactivados. PopulateDiscoveredLabels copia cada etiqueta de grupo de http_sd. __unix_socket__ no es __meta_*, así que sobrevive al borrado de meta. PopulateLabels solo comprueba __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 todavía rechaza / en la dirección. URL() conserva scheme/host/path de __scheme__ / __address__ / __metrics_path__; el socket es solo para el Dial.
Caminos equivocados ya registrados: puntuar esto como NONE dentro del modelo porque SD ya elige targets TCP (la nueva primitiva es IPC del host); tratar unix:///var/run/docker.sock como __address__ como el agujero (es rechazado); afirmar que la configuración de ejemplo por defecto es este remanente. Teatro: una reverse shell. El oráculo es la salud del scrape up en sockets que el JSON de SD nombró.
python3 prometheus-unix-socket-dial-Abraxas-Labs.py
o
cd lab
./run.sh
prom/prometheus:v3.15.0 comparte un netns con un sidecar de Python. El sidecar sirve http_sd en 127.0.0.1:8080/targets.json y dos oráculos HTTP unix: de sistema de archivos /sockets/oracle.sock y abstracto @prom-lab-oracle. Prometheus se publica en 127.0.0.1:19390. La API de admin y el lifecycle permanecen desactivados.
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 y /abstract, ambos con salud up. El slash de TCP nunca llegó a ser un target activo.
No hacer Dial a __unix_socket__ desde SD hasta que sea una ruta de sistema de archivos absoluta y saneada. Rechazar valores vacíos, NUL, nombres abstractos de Linux @, rutas relativas y cualquier .. tras filepath.Clean. Una lista de permitidos de prefijos desde el YAML de scrape es más fuerte. Eliminar la etiqueta de PopulateDiscoveredLabels a menos que provenga de la configuración del operador también cierra la inyección por SD.