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.80 en el commit b282f05.
  • Vector de acceso: endpoint /v1/chat/completions con un modelo con capacidad de visión y una parte de contenido de tipo image_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.
  • internal-admin registra GET /ssrf-marker con peer=172.29.0.30 (el contenedor proxied-router), lo que demuestra que la descarga del servidor llegó al servicio interno.
  • mock-provider recibe POST /api/chat y el flujo completa con HTTP 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.

  1. Construir e iniciar el stack:
bash
docker compose up --build
  1. Confirmar que internal-admin no es accesible desde el host:
bash
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
  1. Enviar la petición denominada POST image-prefetch DNS rebinding trigger desde requests.http, o con curl:
bash
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.