Contexto del incidente
Crawl4AI es un crawler/scraper open source pensado para alimentar pipelines de LLM: instalás el paquete Python, levantás el contenedor Docker y exponés una API REST con autenticación por JWT para que tu aplicación pida crawls sin armar Selenium manualmente. El 21 de junio de 2026, VulnCheck (CNA asignador) publicó CVE-2026-56265, una vulnerabilidad de Use of Hard-coded Credentials (CWE-798) que golpea de lleno ese modo self-hosted.
El Docker API server incluido en todas las versiones de Crawl4AI anteriores a 0.8.7 arranca con una clave JWT por defecto: literalmente la cadena "mysecret". Si el operador no la sobreescribe explícitamente vía variables de entorno, el server firma y valida todos los tokens usando esa clave, que además está visible en el repositorio público (unclecode/crawl4ai). Cualquier atacante que lea el código fuente — o que copie el string de cualquier advisory — puede forjar un JWT válido para el usuario que quiera, incluyendo administradores, y la API lo acepta como legítimo. Vector CVSS v3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — crítico, sin autenticación, sin interacción del usuario. Puntaje: 9.8 (v3.1) / 9.3 (v4.0).
Lo publicaron como CVE-2026-56265 pero el riesgo es mayor: la versión 0.8.7 del 16 de junio de 2026 fue un security-hardening release que cerró nueve vulnerabilidades críticas a la vez en el mismo Docker API. Además del hardcoded JWT, se parchearon dos escapes del AST sandbox que permitían RCE (CVSS 9.8 cada uno), SSRF en webhooks y endpoints de crawl (incluido un bypass via IPv6-mapped [::ffff:169.254.169.254] que esquivaba los naive blocklist), escritura arbitraria de archivos en /screenshot y /pdf, XSS almacenado en el dashboard del monitor, ejecución de JS arbitrario vía /execute_js, y un bypass de autenticación complementario en los WebSocket de /monitor/*. La moraleja operativa: si tenés Crawl4AI expuesto a Internet y no actualizaste, estás jugando en varias mesas a la vez.
Un secreto hardcodeado en código fuente abierto no es una configuración默认值: es una puerta trasera publicada.
¿Cómo proteger tu infraestructura?
1. Actualizá a Crawl4AI 0.8.7 (o superior) ya
El fix directo: el release 0.8.7 elimina la clave por defecto, rechaza claves débiles o cortas al arranque y auto-genera una clave JWT efímera cuando el server arranca sin una configurada. Esto rompe la ventana de explotación del CVE-2026-56265. El upgrade cierra además las ocho vulnerabilidades que se parchearon en el mismo release, así que es la única movida que necesitás para cubrir toda la tanda.
# Si usás el paquete Python
pip install -U "crawl4ai>=0.8.7"
crawl4ai-setup
# Si usás el Docker API server
docker pull unclecode/crawl4ai:latest # imagen 0.8.7+
docker compose down && docker compose up -d
Verificá la versión efectiva dentro del contenedor con docker exec <container> pip show crawl4ai | grep Version antes de seguir, y revisá los logs de arranque: la versión vieja imprime advertencias sobre claves débiles que ya no deberían aparecer.
2. Configurá tu propia clave JWT y rotala
Aunque la 0.8.7 ya no trae mysecret como default, si venís de una versión anterior y nunca seteaste JWT_SECRET, todas las firmas previas fueron hechas con la clave pública — eso significa que tokens viejos pueden seguir circulando y son válidos hasta que cambies la clave. Rotación manual:
# Generar clave nueva (mínimo 32 bytes aleatorios)
openssl rand -base64 48
# En .env o docker-compose.yml
JWT_SECRET="<pegar acá el valor generado>"
JWT_ALGORITHM=HS256
JWT_EXPIRATION=3600
docker compose restart crawl4ai
Forzá claves de al menos 32 bytes, preferentemente 64, y documentá la rotación en tu vault. Si tu Crawl4AI se integra con un IdP externo (OIDC, Authentik, Keycloak), evaluá desactivar la autenticación local por JWT y delegar todo al proxy inverso (Traefik, Caddy, oauth2-proxy).
3. Restringí el acceso de red al Docker API
El server escucha por defecto en 0.0.0.0:11235 dentro del contenedor. Aunque la vulnerabilidad de JWT ya quede parchada, exponer una API con auth por token a Internet sin más es un anti-patrón: cualquier token filtrado (logs, screenshots, dumps de memoria, repos de config) es directamente reusable. Recomendaciones de red:
- Bind a localhost en el host:
ports: ["127.0.0.1:11235:11235"]y exponé el servicio vía un reverse proxy con TLS y autenticación adicional (mTLS, Basic Auth sobre oauth2-proxy). - Firewall saliente estricto: bloqueá las IPs RFC1918 y la metadata
169.254.169.254desde el contenedor para mitigar el SSRF complementario que se parcheó en 0.8.7. - Segmentación: si lo consumís desde otros microservicios, hacelo por red Docker interna (
networks: backend) y nunca desde la red pública del cluster. - Rate limiting en el proxy: 60 req/min por IP para
/auth/loginy/monitor/*desincentiva fuerza bruta sobre JWT.
4. Monitoreá tokens sospechosos y acciones admin no documentadas
Hasta que confirmes que la versión 0.8.7 está corriendo en producción, asumí que cualquier JWT entrante podría haber sido forjado con la clave vieja. Reglas mínimas que deberías cazar en tu SIEM:
- Tokens JWT con header
{"alg":"HS256"}y firma que se valide con la cadenamysecret(decodificás el payload en Python conpyjwty probás). - Conexiones a
:11235/auth/login,:11235/monitor/*o:11235/crawldesde IPs que no están en tu allowlist documentada. - WebSocket hacia
/monitor/wssin query string?token=válido (era el bypass complementario que se parcheó). - Llamadas a
/crawl/jobo/llm/jobconwebhook_urlapuntando a169.254.169.254,metadata.google.internalo cualquier IP privada (vector SSRF del mismo release). - Archivos nuevos en
CRAWL4AI_OUTPUT_DIRcon extensiones.php,.jsp,.aspxo.htmlcreados por el usuario del proceso de Crawl4AI — la 0.8.7 ya no permite traversal con..ni escritura fuera del output dir, pero en versiones viejas es un vector de webshell directo.
Indicadores de compromiso (IoC)
Estos son los indicadores específicos a CVE-2026-56265 y al release 0.8.7 que parchó la tanda completa. Útiles para validar exposición y para investigar incidentes donde sospeches que un operador externo ya descubrió el secreto hardcodeado.
- Clave JWT por defecto (IoC crítico):
"mysecret"— presente enserver/config.pyhasta 0.8.6. Si tu server valida tokens firmados con esta clave, está vulnerable sin parche. - Puerto del Docker API:
11235/tcp— puerto por defecto del self-hosted server; cualquier exposición a0.0.0.0o sin proxy con auth adicional es superficie de ataque. - Endpoints sin token (auth bypass complementario):
/monitor/all_tasks,/monitor/active_tasks,/monitor/clear_tasks,/monitor/ws— en versiones < 0.8.7 responden 200 sin Authorization header. - Payload típico de exploit:
POST /auth/logincon un JWT forjado donde{"sub":"admin","role":"admin","exp":<futuro>}firmado HS256 conmysecret. Devuelve 200 + token admin válido. - SSRF complementario (mismo release): requests a
/crawl,/md,/llmo/crawl/jobconwebhook_urlapuntando ahttp://[::ffff:169.254.169.254]/latest/meta-data/iam/security-credentials/ohttp://metadata.google.internal/computeMetadata/v1/. - Vector RCE sandbox escape (mismo release): expresiones en computed fields que usen
__import__('os').popen(...)o(gi_frame.f_back.f_builtins['__import__'])— payloads JS/Python que devuelven 200 en/config/dumpo/markdown_generatoren versiones vulnerables. - Archivos de webshell (post-explotación):
.php,.phtml,.pharen el directorio configurado comoCRAWL4AI_OUTPUT_DIRo, peor, en/var/www/htmlsi la versión vulnerable permitía traversal con..vía/screenshoto/pdf.
Snippet de validación rápida para que tu equipo de infra confirme si una instancia es vulnerable sin necesidad de explotarla:
import jwt, requests, sys
TARGET = "http://localhost:11235"
SECRET = "mysecret"
# Forjamos un token admin firmado con la clave por defecto
forged = jwt.encode(
{"sub": "admin", "role": "admin", "exp": 9999999999},
SECRET, algorithm="HS256"
)
# Probamos contra /monitor/all_tasks (era público en versiones vulnerables)
r = requests.get(f"{TARGET}/monitor/all_tasks",
headers={"Authorization": f"Bearer {forged}"})
if r.status_code == 200 and "tasks" in r.text.lower():
print(f"[VULNERABLE] {TARGET} acepta tokens firmados con 'mysecret'")
sys.exit(1)
else:
print(f"[OK] {TARGET} rechaza el token o el endpoint requiere auth real")
Si el script devuelve [VULNERABLE], aislá esa instancia de Internet hasta que confirmes versión 0.8.7 o superior y clave JWT rotada — no alcanza solo con parchar, porque los tokens viejos emitidos con mysecret siguen siendo válidos mientras el server conserve esa clave en memoria o en disco.
🛡️ ¿Tu Crawl4AI está expuesto a Internet sin clave JWT propia?
En IPSecureNetwork hacemos auditorías de superficie expuesta y revisiones de configuración de servicios self-hosted. Si tenés Crawl4AI, n8n, Ollama, Open WebUI o cualquier otro servicio con auth por tokens corriendo en tu infra, te armamos un informe con qué CVE le pega hoy y cómo contenerlo.
SOLICITAR AUDITORÍA →