Tailworm – Tailscale Vulnerabilities
The design choices of the open source tailscale and headscale software
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
The design choices of the open source tailscale and headscale software have created an environment ripe for risk, while marketing security as its design and purpose. Choices have been made to move away from security-in-depth, removing layers of authentication, and replacing daemons such as OpenSSH which employ privilege-separation with a highly-privileged process. Other findings include broader-than-advertised network security boundaries, which seems to be legitimate bugs, but the missing security and isolation is precisely what users are advertised by these tools.
Regarding disclosure timelines, it is of the researcher's opinion that infrastructure is vulnerable today. This report strictly audits the open source software provided to users under no condition of warranty per the BSD-3 license. As such, the researchers, and the users, are effectively developers of said software per its open source nature.
The software in question has a corporate sponsor. Tailscale Inc., is a for-profit organization employing developers to build secure software. The researchers are not associated with the Tailscale corporation, are not employed by said organization, and have not been contracted or paid to conduct or report this research. All research was performed against open source software on the systems and devices of the researchers.
It is the opinion of the researchers that rapid awareness of these flaws will expediate remediation across impacted devices. These vulnerabilities were not introduced by the researchers, but were discovered, and discovered with low effort.
-- Erica Windisch, Cyberdione Labs Corporation
Testing window: 2026-10-22 to 2026-10-24. All findings were verified by reproduction in a lab environment on unmodified, released software (tailscale v1.104.2, headscale v0.29.5). Lab and organizational identifiers are redacted. Finding identifiers: T1–T3, C1–C4, and F-1–F-5 are internal audit labels, retained so the reproduction kits can be cross-referenced.
What was audited. Tailscale client agent v1.104.2 (tailscaled, its embedded Tailscale SSH server, its serve/funnel feature, and its DERP relay client), the headscale v0.29.5 self-hosted control server, the gliderssh library both build on, and the experimental conn25 app-connector feature. Findings fall into five classes; each names the component it affects.
How initial intrusion plausibly occurs. Class 1 assumes the attacker already holds a foothold on one device — which happens the ordinary ways: an exploited web application (a vulnerable PHP page or CMS plugin), a malicious build dependency (npm/PyPI package, Docker base image, GitHub Action), a phishing implant, a supply-chain compromise, or stolen identity credentials. Tailscale does not create the foothold; the finding is that the mesh converts one ordinary foothold into fleet-wide privileged access at machine speed, because inter-device paths are granted by policy that assumes trusted endpoints while the agent's privileged operations are reachable from unprivileged sessions.
Practical risk in default deployments. With Tailscale SSH enabled (opt-in per node, often fleet-wide): one compromised account reaches root on other servers (class 1) and, in default (Tailnet-Lock-disabled) configurations, joins an attacker machine as a legitimate tagged server with routes auto-approved. With app connectors configured: policy-invisible reach into cloud credentials and Kubernetes services (class 2). With a self-hosted, internet-reachable headscale: unauthenticated crash loop and onboarding lockout (class 3). Any client pointed at a hostile relay: agent exhaustion (class 4). Any node with the serve forward configured: remote admin-API exposure (class 5).
Wormability. Measured in a lab: policy-granted Tailscale SSH plus class-1 findings allow automated spread to every device the policy permits — no passwords, no stolen keys, no user interaction, under a second per hop. Two structural limits held: per-pair access grants bound the spread (the single control that stopped it), and hardware-bound device credentials (TPM/Secure Enclave) prevent the identity theft that makes compromise persist. All other controls changed visibility speed, not spread.
Mitigations, in order of effect. (1) Per-pair SSH grants — never identity-wide or tag-wide accept rules. (2) Disable SSH forwarding: on SaaS, set the per-rule forwarding fields to false; on headscale v0.29.5 forwarding cannot be disabled — it is granted unconditionally on every accepted session, so until fixed, treat every headscale SSH grant as root-equivalent. Restricting SSH to non-privileged accounts is defense-in-depth only — it does not mitigate these findings, which operate from fully unprivileged accounts. (3) Node-key expiry on server fleets; TPM/Secure-Enclave-bound agent state. (4) Keep security telemetry on infrastructure a compromised server cannot reach — self-tested, with coverage reporting. (5) Restrict connector entitlements; destination allowlists or Kubernetes default-deny egress on the connector namespace. (6) Incident response built on device deletion at the control plane: a node's keys live in a file an attacker with root can copy, and reimaging the host does not revoke an already-stolen copy — only control-plane deletion (or key expiry) does. (7) Self-hosted control planes: memory limits, restart supervision, registration-rate throttling. Vendor fixes are small and precedented (OpenSSH, Dropbear); the above protects you before and after they ship.
Tailscale SSH performs no user-level authentication. The SSH protocol's authentication phase completes with method none: the WireGuard layer has already cryptographically identified the device, and that device identity — not a password, key pair, or user credential — is what the SSH policy evaluates. Authorization is therefore exactly one policy rule (source device to target device, with target accounts named in the rule), and a session established under this model carries the forwarding capabilities of findings 1–3 for its lifetime. Two consequences for threat modeling: an attacker needs no credential material beyond a foothold on any policy-permitted device; and device compromise is user-agnostic — the malware of any user on an entitled device speaks with the device's full authorization. This model is also what makes the wormability measured below possible: propagation required no passwords and no stolen keys.
Two design properties give an attacker complete targeting knowledge at zero risk of detection:
Together these mean an attacker's targeting is not discovery-limited: the mesh hands them the target list, and a quiet sweep hands them the policy map. Findings 1–4 then convert that map into access.
Tailscale's app-connector facility lets an organization route traffic to external SaaS services (or internal applications) through designated gateway devices, so that access to those services appears to come from the organization's addresses and can be governed in one place. Administrators designate connector devices, define the apps (named service definitions, typically domains), and grant selected devices an entitlement to use a given app through the connector. In intended operation, a client resolves an app's domain, is directed to a connector, and the connector forwards the flow to the app's real destination.
conn25 is a redesigned connector mechanism (present but experimental in v1.104.2) built on per-flow mappings: an entitled client registers a mapping with the connector over the tailnet's peer API — "when I send traffic to transit address X, translate it to real destination Y" — and the connector installs packet-filter rules and performs the translation in its data path. Two implementation properties matter to finding 4: the client-supplied destination Y is validated only for address family (never against the app's configured destinations), and the connector's ingress rules accept transit-pool traffic before the tailnet's access-control evaluation runs. In production the mechanism is deployed principally through the Kubernetes operator, which runs connector pods with full container privileges positioned across the cluster's service and pod networks.
For threat modeling: the entitlement an administrator grants is "this device may use app X through the connector" — but nothing in the implementation binds translated destinations to app X. The reader should assess finding 4 with that gap in mind: the feature delegates routing decisions to the client while the authorization decision was made about the app.
Affected component / version: Tailscale SSH server in tailscaled v1.104.2 (gliderssh streamlocal local forwarding). Prerequisites: an SSH policy rule accepting the attacker's connection to the target, with local forwarding permitted. No sudo, docker-group membership, or victim-side daemon access is required.
An authenticated peer with an SSH policy rule permitting local forwarding can cause the root-privileged tailscaled process to connect, on the peer's behalf, to unix sockets restricted to root:
ssh -fNT -L /tmp/relay.sock:/tmp/docker.sock user@target # dockerd at an allowlisted-dir socket docker -H unix:///tmp/relay.sock run --rm -v /:/host busybox sh -c 'id; head -c 40 /host/etc/shadow'
Verified result: uid=0(root) plus /etc/shadow contents, in 0 s measured elapsed time, with no sudo, docker group, or victim daemon involved. Scope note: the static path allowlist {home, /tmp, /run/user} is bypassed by finding 3. Reproduction: poc-docker/tailscale-privesc.sh — a single-command demonstration of finding 1 (negative control plus exploitation chain), shipped byte-exact as originally authored per the operator, for hash continuity with the previously published commitment. sha256: c84d461603a2b48023606149b6808e3dfc0733abc6cc38d4b52e0618c3851a2d Remediation: see "Recommended remediation" (findings 1–3 share one root cause).
Affected component / version: Tailscale SSH server in tailscaled v1.104.2 (gliderssh streamlocal remote forwarding; BindUnlink behavior). Prerequisites: as finding 1, with remote forwarding permitted. Impact: the root-privileged tailscaled unlinks a sticky-directory-protected, root-owned socket and binds the attacker's forwarding listener at that path, impersonating the service. For command-channel daemons this is direct root code execution (verified: one crafted message → uid=0). For Docker: cleartext registry-credential capture (cleartext POST observed) plus control over pulls of unpinned tags; host root requires a further engagement step (tag poisoning using the captured credentials). Reproduction: poc-docker/ (environment setup, both commands, expected outputs; verified lab demonstration). Remediation: see "Recommended remediation" (findings 1–3 share one root cause).
Affected component / version: Tailscale SSH server in tailscaled v1.104.2 (validateAndResolve{Listen,Socket}Path path validation in gliderssh). Prerequisites: as findings 1–2 (an SSH accept rule with forwarding permitted; the racer runs unprivileged inside the attacker's SSH session).
validateAndResolve{Listen,Socket}Path validates the lexical path at check time; the unlink/bind and the dial occur later, when the kernel re-resolves the path. A rename(2) race loop (~170k path-state flips/s) running in the SSH session can win the check-to-use gap:
- Bind side: the root tailscaled unlinked the live root-owned control socket under /run and bound the attacker's forward at the real path (0600 listener fingerprint); root CLI localapi traffic was forwarded off-host; a byte-exact POST /localapi/v0/start containing an authkey was captured (enrollment not executed). Reproduction: poc-t2race/ (racer.py + attack.sh; expected: victim socket inode replaced, captured request in capture.log).
- End-to-end root execution via raced stock dockerd (GAP-A1): a single raced keep-alive connection through -L to /run/docker.sock on the stock systemd-managed Docker daemon completed cont
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。