
CVE-2026-13233 (Drupal OpenAI Provider, SA-CONTRIB-2026-053): SSRF de response-URL / lectura de archivos locales. Upstream no confiable, no el prompt. Reproductor seguro + detecciones. Corregido en 1.1.1/1.2.2.
Upstream no confiable: el proveedor que descargó la URL que su propia respuesta le indicaba.
Drupal OpenAI Provider (ai_provider_openai) · SSRF (CWE-918) · CVE-2026-13233 · SA-CONTRIB-2026-053
Estado: Divulgación coordinada completa (2026-07-10). Notificado de forma privada al equipo de seguridad de Drupal, corregido y publicado como CVE-2026-13233 / aviso SA-CONTRIB-2026-053 (calificado como Moderadamente crítico). El autor figura como descubridor y desarrollador de la corrección.
Un estudio de un defecto clásico de autorización / validación de entrada en un "punto de conexión" de IA: un
proveedor de generación de imágenes por IA descargaba la URL contenida en la respuesta de la API upstream mediante una
función de propósito general sin lista blanca de esquemas. En despliegues donde el upstream configurado
no es el predeterminado de confianza (trae-tu-propio-endpoint / proxy / gateway auto-alojado / relay
comprometido), la respuesta podía apuntar el servidor a file:// o a una dirección interna.
La entrada no confiable que llega al sumidero (sink) es la URL de la respuesta del upstream — no el prompt del usuario.
file://; el
análisis interno del autor también lo asignó a CWE-73 / CWE-441.3.1 / condicional 5.3
(Medium); los anteriores 6.5 / 7.4 retractados, y la calificación media lo confirmó.ai_provider_openai) < 1.1.1 y 1.2.0–1.2.1 (auditado 1.2.1) en Drupal core 11.2Toda la verificación se ejecutó en un sandbox propio, aislado de la red, contra un upstream simulado (mock). La API real de OpenAI nunca se llamó. Los oráculos de fuga eran solo sintéticos.
Confirmado (demostrado dinámicamente):
settings.php confirmada por coincidencia de bytes sha256 (35,182 B) — el contenido nunca se imprimió; por lo tanto,
las credenciales de la base de datos están dentro del alcance.No verificado (explícitamente no reclamado):
No afectado: api.openai.com predeterminado sobre TLS. El ajuste host está controlado por el administrador / config-sync / despliegue
— un atacante no administrador en tiempo de ejecución no puede cambiarlo.
La URL de la respuesta se pasaba a una descarga de propósito general que respeta todos los wrappers de flujo de PHP
registrados (file, php, http, data, phar, …). response_format tiene como valor predeterminado url, por lo que la rama
vulnerable es la ruta predeterminada. file:// es el wrapper de sistema de archivos predeterminado de PHP y no está controlado por
allow_url_fopen (una afirmación anterior de "requiere allow_url_fopen" era incorrecta y queda retractada).
Corrija la primitiva, no solo el punto de llamada:
http/https antes de la solicitud; deshabilite las redirecciones (un 30x puede rebotar a file://).file://…, http://169.254.169.254/…, y una
redirección 302→file:// deben ser rechazadas.Dirección completa: docs/fix-direction.md.
.
├── README.md # this file (English, canonical)
├── README_ja.md # Japanese mirror
├── SECURITY.md # reporting policy + responsible-disclosure disclaimer
├── .gitignore # blocks secrets / primary evidence from being committed
├── docs/
│ └── fix-direction.md # root-cause fix + variant hunt + minimal-diff sketch (EN)
├── reproducer/ # SAFE reproducer only (sandbox, mock upstream, benign oracle)
│ ├── README.md # how to stand up the network-isolated sandbox
│ ├── verdict.md # what "success" means: raw-byte match + 200-vs-403 + exit codes
│ ├── mock-openai-server.py # benign mock; default happy-path PNG, ssrf_demo → file:///etc/hostname only
│ └── docker-compose.yml # TEMPLATE: isolated Drupal sandbox + mock (you add the vulnerable target)
├── detections/
│ ├── README.md
│ ├── sigma/web-egress-to-internal-after-imagegen.yml
│ ├── sigma/php-sensitive-file-open.yml
│ ├── sentinel/imagegen-egress-correlation.kql
│ └── splunk/imagegen-content-type-mismatch.spl
└── timeline.md # disclosure timeline (single source of truth)