🐛 Tailworm — Fallos de Tailscale SSH (Root sin Parche)
Veredicto en una línea
El informe es real y público, pero el titular «NO PATCH» necesita matiz: no hay parche en una versión publicada (la última es v1.104.2, del 21-oct), y sí hay fixes mergeados en
maindesde el 24–26 de octubre. Tampoco es un gusano remoto: hace falta un foothold previo en un dispositivo que la política ya autorice.
Ficha rápida
- Informe: Tailworm, de Erica Windisch (Cyberdione Labs), publicado el 25-oct-2026.
- Probado en: Tailscale v1.104.2 y headscale v0.29.5, en laboratorio, del 22 al 24 de octubre.
- CVE: ninguno asignado.
- Boletín de Tailscale: ninguno (TS-2026-013, del 21-oct, es otro tema: certificados eMudhra).
- Parche publicado: no. Fix en
main: sí, PRs #21941 y #21942 mergeados el 26-oct; #21959 abierto.- Requisito: Tailscale SSH activo con port forwarding, y acceso ya permitido por la ACL a un dispositivo comprometido.
- Impacto: de sesión SSH sin privilegios a root en el host destino; captura de authkeys y nodo rogue en la tailnet.
🕒 Qué pasó
| Fecha | Evento |
|---|---|
| 22–24 oct | Ventana de pruebas en laboratorio sobre software sin modificar: Tailscale v1.104.2 y headscale v0.29.5. |
| 24 oct (sáb) | Windisch envía el informe y el PoC al contacto de seguridad de Tailscale, según su propia declaración. |
| 25 oct | Publica tailworm.com con 11 hallazgos en 5 clases. The Hacker News cubre la noticia el mismo día. |
| 25 oct | Tailscale mergea ipn/ipnlocal: fix serve app caps leaking across mounts (#21931), que corresponde al hallazgo 11. |
| 25–26 oct | Mergean la serie de privilege drop para forwarding SSH (#21941, #21942) y actualizan gliderssh por agotamiento de recursos. |
| 26 oct | Abren #21959 para que el payload de los forwards Unix viaje con la identidad del usuario, no con la de root. |
| 26 oct | Abren en headscale #3586 citando Tailworm: limita la presión de registros que expulsaba aprobaciones pendientes. |
Disclosure
Windisch lo describe como «reported but not coordinated». Dice que sí entregó informe y PoC antes de publicar, y que había advertido al CEO sobre la arquitectura. Parte de la comunidad lo criticó por publicar sin coordinación; otros defendieron que el riesgo era inmediato.
🧠 Cómo funciona el ataque
Tailscale SSH no autentica usuarios: autentica el dispositivo a nivel WireGuard y luego aplica una sola regla de política. Si esa regla permite forwarding, la sesión hereda esas capacidades. El problema: el servidor SSH vive dentro de tailscaled, que corre como root.
flowchart LR A["Foothold en 1 device<br/>(web, dependencia, phishing)"] --> B["Sesión SSH aprobada<br/>sin sudo ni claves"] B --> C["-L / -R hacia socket Unix<br/>(lo ejecuta tailscaled = root)"] C --> D["docker.sock · systemd/private<br/>tailscaled localapi"] D --> E["root en el host"] D --> F["Captura de authkey"] F --> G["Nodo rogue unido al tailnet"]
Los tres fallos de base
| # | Mecanismo | Resultado verificado en laboratorio |
|---|---|---|
| 1 | ssh -L a socket Unix: el dial lo hace root | docker.sock → uid=0 y lectura de /etc/shadow, en 0 s, sin sudo |
| 2 | ssh -R reemplaza sockets root-owned (BindUnlink) | Ejecución root contra daemons de canal de comandos; captura de credenciales de registry en Docker |
| 3 | TOCTOU: la ruta se valida y luego se usa | Rebind del socket de control → captura de POST /localapi/v0/start con authkey → nodo rogue online |
La cadena completa
- El atacante consigue un foothold normal en un dispositivo.
- Abre una sesión SSH permitida por la política — sin contraseña ni clave.
- Usa forwarding Unix para llegar a un socket privilegiado.
- Con el rebind del socket de control captura una authkey de enrolamiento.
- Une su propia máquina como servidor etiquetado, con rutas autoaprobadas.
Los dos frenos que sí funcionaron
- Reglas por par (un origen → un destino concreto): fue el único control que detuvo la propagación en las pruebas.
- Claves de dispositivo ligadas a hardware (TPM / Secure Enclave): impiden que el robo de identidad persista.
Tailnet Lock bloqueó el paso final de enrolamiento, aunque los secretos sí se capturaron en tránsito.
📋 Los 11 hallazgos
| Clase | Hallazgos | Qué rompe | Severidad del informe |
|---|---|---|---|
| SSH forwarding | 1, 2, 3 | Forwarding Unix corre como root; allowlist de rutas evadible por carrera | High |
| App connectors (conn25) | 4 | El destino lo declara el cliente y no se valida; se alcanza IMDS, LAN y servicios de Kubernetes | High |
| Serve | 7, 8, 9, 10, 11 | serve --tcp al localapi, guard solo en config, Funnel sin atributo, bind fuera del túnel, bleed de cabeceras entre mounts | High / Medium |
| headscale | 6 | DoS no autenticado: evicción de aprobaciones pendientes y consumo de memoria | Medium-High |
| DERP client | 6 | Lectura sin límite de un relay hostil: 10 MB → 1,34 GB | Medium-High |
Detalles que importan
- #4, conn25: una entitlement de usuario («puedes usar la app X») se convierte en capacidad de elegir cualquier destino de salida. En Kubernetes el connector corre privilegiado y su posición abarca todo el Service CIDR y el pod CIDR.
- #5: session pin en gliderssh heredado de
gliderlabs/ssh: ~536 MB en 7 s en el daemon root. - #6: en headscale, 1024 registros minúsculos expulsan aprobaciones pendientes; el
ReadAllde derphttp convierte 10 MB en 1,34 GB. - #7:
tailscale serve --bg --tcp <puerto> unix:/run/tailscale/tailscaled.sockes aceptado; un peer autorizado obtiene 200 enGET /localapi/v0/statusyPATCH /v0/prefs→ toma de control del nodo, sin carrera. - #11: en HTTP/2, el contexto de serve se comparte entre streams; 8 de 4.000 requests a
/admindevolvieron 200 con la cabecera de capabilities de otro mount.
⚖️ ¿Qué tan grave es? Lectura crítica
Lo que sí está sólido
El privilege escalation es real en el diseño: el forwarding Unix se ejecuta en un proceso root, sin privilege separation, y el propio fix de Tailscale lo confirma al moverlo a procesos hijos con las credenciales del usuario. Los hallazgos 1–3 y 7 son la parte más fuerte del informe.
Lo que hay que tomar con pinzas
- «Wormable» se midió en laboratorio, no en redes reales. Investigadores en HN lo cuestionan: sin una regla que permita el siguiente salto, no hay propagación.
- Un solo investigador, sin reproducción independiente y sin confirmación técnica de Tailscale.
- La enumeración de toda la tailnet que describe el informe es más propia de headscale. Tailscale aplica netmap trimming: si no hay ACL entre dos nodos, no se ven.
- El estilo del informe (muy largo, con tono de auditoría) generó rechazo y le restó tracción, aunque no invalida los PoC.
Lo que dice Tailscale
- Mike Shaver (VP de Ingeniería), en Reddit: recibieron el informe, ya estaban revisando esa área con especialistas externos, encontraron problemas y están trabajando en el fix. Todavía delimitan qué configuraciones se ven afectadas. No recomienda que todo el mundo desactive Tailscale SSH.
- raggi (Tailscale), en HN: pidió matizar la enumeración por el netmap trimming y confirmó que habrá más detalles.
🔍 ¿Estás expuesto?
La exposición alta requiere Tailscale SSH con forwarding. Si solo usas la malla para conectar el VPS con tu equipo y no activaste Tailscale SSH, el hallazgo principal no te aplica.
# ¿Este nodo corre el servidor SSH de Tailscale?
tailscale debug prefs | grep -i -E 'RunSSH|ssh'
# ¿Hay forwards de serve hacia sockets Unix?
tailscale serve status
# ¿Qué peers ves y cuáles responden?
tailscale statusEn el admin console:
- Access controls → busca reglas
sshconaction: accept. - Machines → revisa qué nodos tienen SSH habilitado.
- DNS / Serve → confirma que no haya forwards a
unix:.
Señales de alarma
- Reglas
sshconautogroup:member,tag:...o*como origen.- Forwarding permitido sin necesidad real.
tailscale serveapuntando a/run/tailscale/tailscaled.sock,docker.socko sockets de systemd.- headscale expuesto a Internet sin límites de memoria ni rate limiting.
🛡️ Mitigaciones (sin parche publicado)
Ordenadas por reducción de riesgo:
| # | Acción | Por qué |
|---|---|---|
| 1 | Reglas SSH por par: un dispositivo origen → un destino | Único control que detuvo la propagación en el laboratorio |
| 2 | Desactivar forwarding por regla (SaaS) | Corta los hallazgos 1–3 de raíz |
| 3 | En headscale 0.29.5, tratar cada grant SSH como root | Ahí el forwarding no se puede desactivar |
| 4 | Quitar permisos de app connectors que no se usen; default-deny de egreso en el namespace del connector | Contiene conn25 (IMDS, LAN, Kubernetes) |
| 5 | Borrar forwards de serve hacia sockets Unix | Evita el hallazgo 7, que es toma de control directa |
| 6 | Expiración de node keys + claves ligadas a TPM/Secure Enclave | Limita el valor de una clave robada |
| 7 | Si un equipo se compromete, bórralo en el control plane | Reinstalar no revoca claves ya copiadas |
| 8 | SSH check mode + auto-updates | Añade fricción, no arregla el escalado |
| 9 | headscale: límites de memoria, supervisión de reinicio, throttling de registros | Mitiga el DoS no autenticado |
| 10 | Telemetría fuera del host comprometido | Un root en el servidor puede borrar sus propias huellas |
La lección de fondo
El problema no es «Tailscale es inseguro», sino concentrar el modelo de identidad y un servidor SSH privilegiado en el mismo proceso root. La defensa en profundidad importa: si el forwarding se ejecutara con el uid del usuario, el fallo sería mucho menor. Ese es exactamente el fix que Tailscale está mergeando.
🧑💻 Qué está haciendo Tailscale
Todo el trabajo referencia el issue interno tailscale/corp#50380:
| Cambio | Estado | Qué arregla |
|---|---|---|
| #21941 — hijos de forwarding con credenciales de usuario y ops de dial/listen en el monitor | ✅ Mergeado 26-oct | Base del privilege drop |
| #21942 — enrutar el forwarding SSH por el monitor privilegiado | ✅ Mergeado 26-oct | Los hallazgos 1–3: el socket lo abre el usuario, no root |
| #21959 — relay del payload Unix como el usuario vinculado | 🟡 Abierto 26-oct | Cierra la fuga de credenciales SCM en el payload |
| #21931 — app caps de serve por request | ✅ Mergeado 25-oct | Hallazgo 11 |
| gliderssh actualizado por agotamiento de recursos y data race | ✅ Mergeado 25-oct | Hallazgo 5 |
| headscale #3586 — limitar autenticaciones pendientes sin evictar sesiones | 🟡 Abierto 26-oct | Hallazgo 6 |
Versión con el fix: aún ninguna. La última release es v1.104.2 (21-oct). El próximo release debería incluirlo; queda por confirmar el número y la fecha.
Sobre
tailscale serveEl fix de #21931 es la respuesta exacta al hallazgo 11. Para el #7 (
serve --tcpal localapi), la mitigación sigue siendo no configurar forwards a sockets privilegiados.
🧾 Conclusión
- La vulnerabilidad existe y está bien demostrada: forwarding Unix en un daemon root convierte una sesión sin privilegios en root.
- No es un gusano de Internet: exige un foothold y una ACL que permita el salto.
- «Sin parche» es correcto para releases, pero incorrecto como estado del proyecto: el fix está en
maindesde el 26-oct. - La ventana de riesgo real es para quien corre headscale 0.29.5 con SSH y forwarding, o tiene reglas SSH amplias por identidad/tag.
- La acción más rentable hoy: reglas por par + forwarding desactivado + limpiar
serve.
🔗 Enlaces
- Tailworm — informe original
- The Hacker News — cobertura (25-oct-2026)
- Tailscale — Security Bulletins
- Tailscale — Changelog
- Tailscale — PR #21942 · PR #21941 · Issue #21959
- headscale — PR #3586
- Hacker News — discusión
- Reddit — respuesta de Mike Shaver
🔗 Notas relacionadas
- 🚨 PHP2Shell — otra vulnerabilidad crítica con ventana de exposición y mitigaciones.
- 📰 Boletín IA y Ciberseguridad — contexto de la semana en seguridad.
- 🧠 Gemini 4 Argon — montaje con VPS y Tailscale.
- 🆓 Free for Dev — alternativas de túneles y mesh.
- 📥 Vault Ingest — cómo se guardó esta nota.