Descripción general
9router valida las URLs de imagen resolviendo el host antes de realizar la descarga, pero la descarga posterior en el lado del servidor ejecuta una resolución DNS separada e independiente. Un nombre DNS controlado por el atacante puede resolver a una IP pública durante la validación y luego redirigirse a una IP interna de Docker o de red privada durante la descarga efectiva. Esto permite que el prefetch de imágenes en el servidor alcance servicios HTTP exclusivamente internos, constituyendo una vulnerabilidad de tipo SSRF.
Detalles técnicos
- Versión afectada: 9router
v0.4.80en el commitb282f05. - Vector de acceso: endpoint
/v1/chat/completionscon un modelo con capacidad de visión y una parte de contenido de tipoimage_url. Se requiere un modelo con capacidad de visión para que la imagen supere el filtrado de modalidad y se active el prefetch en el servidor. - El proveedor utilizado en la reproducción es el mock provider incluido en el paquete. No se requiere ninguna clave de API real ni se realiza ninguna llamada a un proveedor real.
- internal-admin (el objetivo del SSRF) no está expuesto a la red del host; solo es accesible desde dentro de la red Docker.
- Comportamiento del servidor rebind-dns para
rebind.9r.test:- Primera respuesta A:
1.1.1.1(pública), para superar la validación de host público. - Segunda respuesta A:
172.29.0.10(internal-admin), durante la descarga efectiva.
- Primera respuesta A:
- internal-admin registra
GET /ssrf-markerconpeer=172.29.0.30(el contenedorproxied-router), lo que demuestra que la descarga del servidor llegó al servicio interno. - mock-provider recibe
POST /api/chaty el flujo completa conHTTP 200.
Causa raíz
La causa raíz es una condición de carrera de tipo DNS TOCTOU (Time-Of-Check Time-Of-Use): la IP resuelta no se fija entre la resolución de validación (la comprobación de host público) y la resolución de descarga. La comprobación y la descarga resuelven el nombre de host de forma independiente, por lo que una autoridad DNS con TTL 0 puede devolver una IP pública a la comprobación y una IP interna a la descarga.
Prueba de concepto
Este repositorio es una reproducción autocontenida con Docker Compose. No se llama a ningún proveedor real y no se requiere ninguna clave de API real.
- Construir e iniciar el stack:
docker compose up --build
- Confirmar que
internal-adminno es accesible desde el host:
curl -i http://127.0.0.1:18083/ssrf-marker # connection refused / fail
docker compose ps # internal-admin no tiene mapeo de puerto al host
- Enviar la petición denominada
POST image-prefetch DNS rebinding triggerdesderequests.http, o con curl:
curl -i -X POST http://127.0.0.1:18082/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "ollama-local/gemma3",
"messages": [{"role":"user","content":[
{"type":"text","text":"reproduction image-prefetch trigger"},
{"type":"image_url","image_url":{"url":"http://rebind.9r.test:8080/ssrf-marker?case=rebind-trigger"}}
]}],
"stream": false
}'
Impacto
- SSRF hacia servicios HTTP internos accesibles desde el host o contenedor de 9router.
- Dependiendo del entorno, esto puede alcanzar endpoints de metadatos de nube, paneles de administración internos, o utilizarse para descubrimiento de servicios internos.
- SSRF ciego o semi-ciego cuando la respuesta descargada no se devuelve al atacante. Una variante de exfiltración, apuntando la imagen a un endpoint interno que devuelva bytes de imagen válidos, puede retornar contenido interno codificado en base64 al upstream.
- Requiere una ruta de código que realice prefetch o normalización de imágenes remotas para proveedores con capacidad de visión.
- No se necesita ninguna credencial real para la reproducción.
Mitigaciones sugeridas
- Fijar la IP resuelta tras la validación y conectar a esa IP concreta: resolver una sola vez y reutilizar la dirección para la descarga.
- Bloquear rangos privados, de loopback, link-local, multicast y de metadatos de nube en el momento de la conexión, no solo en el momento de la validación.
- Realizar la resolución DNS y las comprobaciones de IP inmediatamente antes de la petición y contra la dirección efectivamente utilizada para conectar.
- Deshabilitar las redirecciones, o revalidar cada destino de redirección con las mismas comprobaciones.
- Aplicar una lista de dominios permitidos para la descarga de imágenes cuando sea viable.
- Añadir un tiempo de espera máximo, un límite de tamaño de respuesta y una comprobación del tipo de contenido.
