🐛 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 main desde 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ó

FechaEvento
22–24 octVentana 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 octPublica tailworm.com con 11 hallazgos en 5 clases. The Hacker News cubre la noticia el mismo día.
25 octTailscale mergea ipn/ipnlocal: fix serve app caps leaking across mounts (#21931), que corresponde al hallazgo 11.
25–26 octMergean la serie de privilege drop para forwarding SSH (#21941, #21942) y actualizan gliderssh por agotamiento de recursos.
26 octAbren #21959 para que el payload de los forwards Unix viaje con la identidad del usuario, no con la de root.
26 octAbren 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

#MecanismoResultado verificado en laboratorio
1ssh -L a socket Unix: el dial lo hace rootdocker.sock → uid=0 y lectura de /etc/shadow, en 0 s, sin sudo
2ssh -R reemplaza sockets root-owned (BindUnlink)Ejecución root contra daemons de canal de comandos; captura de credenciales de registry en Docker
3TOCTOU: la ruta se valida y luego se usaRebind del socket de control → captura de POST /localapi/v0/start con authkey → nodo rogue online

La cadena completa

  1. El atacante consigue un foothold normal en un dispositivo.
  2. Abre una sesión SSH permitida por la política — sin contraseña ni clave.
  3. Usa forwarding Unix para llegar a un socket privilegiado.
  4. Con el rebind del socket de control captura una authkey de enrolamiento.
  5. 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

ClaseHallazgosQué rompeSeveridad del informe
SSH forwarding1, 2, 3Forwarding Unix corre como root; allowlist de rutas evadible por carreraHigh
App connectors (conn25)4El destino lo declara el cliente y no se valida; se alcanza IMDS, LAN y servicios de KubernetesHigh
Serve7, 8, 9, 10, 11serve --tcp al localapi, guard solo en config, Funnel sin atributo, bind fuera del túnel, bleed de cabeceras entre mountsHigh / Medium
headscale6DoS no autenticado: evicción de aprobaciones pendientes y consumo de memoriaMedium-High
DERP client6Lectura sin límite de un relay hostil: 10 MB → 1,34 GBMedium-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 ReadAll de derphttp convierte 10 MB en 1,34 GB.
  • #7: tailscale serve --bg --tcp <puerto> unix:/run/tailscale/tailscaled.sock es aceptado; un peer autorizado obtiene 200 en GET /localapi/v0/status y PATCH /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 /admin devolvieron 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 status

En el admin console:

  • Access controls → busca reglas ssh con action: accept.
  • Machines → revisa qué nodos tienen SSH habilitado.
  • DNS / Serve → confirma que no haya forwards a unix:.

Señales de alarma

  • Reglas ssh con autogroup:member, tag:... o * como origen.
  • Forwarding permitido sin necesidad real.
  • tailscale serve apuntando a /run/tailscale/tailscaled.sock, docker.sock o 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ónPor qué
1Reglas SSH por par: un dispositivo origen → un destinoÚnico control que detuvo la propagación en el laboratorio
2Desactivar forwarding por regla (SaaS)Corta los hallazgos 1–3 de raíz
3En headscale 0.29.5, tratar cada grant SSH como rootAhí el forwarding no se puede desactivar
4Quitar permisos de app connectors que no se usen; default-deny de egreso en el namespace del connectorContiene conn25 (IMDS, LAN, Kubernetes)
5Borrar forwards de serve hacia sockets UnixEvita el hallazgo 7, que es toma de control directa
6Expiración de node keys + claves ligadas a TPM/Secure EnclaveLimita el valor de una clave robada
7Si un equipo se compromete, bórralo en el control planeReinstalar no revoca claves ya copiadas
8SSH check mode + auto-updatesAñade fricción, no arregla el escalado
9headscale: límites de memoria, supervisión de reinicio, throttling de registrosMitiga el DoS no autenticado
10Telemetría fuera del host comprometidoUn 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:

CambioEstadoQué arregla
#21941 — hijos de forwarding con credenciales de usuario y ops de dial/listen en el monitor✅ Mergeado 26-octBase del privilege drop
#21942 — enrutar el forwarding SSH por el monitor privilegiado✅ Mergeado 26-octLos hallazgos 1–3: el socket lo abre el usuario, no root
#21959 — relay del payload Unix como el usuario vinculado🟡 Abierto 26-octCierra la fuga de credenciales SCM en el payload
#21931 — app caps de serve por request✅ Mergeado 25-octHallazgo 11
gliderssh actualizado por agotamiento de recursos y data race✅ Mergeado 25-octHallazgo 5
headscale #3586 — limitar autenticaciones pendientes sin evictar sesiones🟡 Abierto 26-octHallazgo 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 serve

El fix de #21931 es la respuesta exacta al hallazgo 11. Para el #7 (serve --tcp al localapi), la mitigación sigue siendo no configurar forwards a sockets privilegiados.


🧾 Conclusión

  1. La vulnerabilidad existe y está bien demostrada: forwarding Unix en un daemon root convierte una sesión sin privilegios en root.
  2. No es un gusano de Internet: exige un foothold y una ACL que permita el salto.
  3. «Sin parche» es correcto para releases, pero incorrecto como estado del proyecto: el fix está en main desde el 26-oct.
  4. 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.
  5. La acción más rentable hoy: reglas por par + forwarding desactivado + limpiar serve.

🔗 Enlaces

🔗 Notas relacionadas