Tailworm:Tailscale SSH 提权至 root
Tailscale SSH 的 root 守护进程为获准用户连接和替换 Unix 套接字,可在策略允许的服务器上取得 root;升级至 1.104.4 或更高版本,并在更新前禁用转发。
要点速览
- Tailscale SSH 的内嵌服务器运行于
tailscaled内部,这是一个以 root 运行的守护进程,并使用 root 凭据执行 Unix 套接字转发操作:连接、绑定和删除链接。获准的 SSH 会话可借此访问仅限 root 的套接字:研究人员演示了通过docker.sock执行 root 命令、通过 systemd 控制套接字获得 root shell,以及捕获用于注册新设备的密钥。 - 审计测试的是已发布软件:Tailscale 客户端 1.104.2、headscale 0.29.5,以及 Tailscale 内嵌的 gliderssh 库。Tailscale 于 10 月 27 日在 1.104.4 中发布修复,并于 10 月 28 日发布其调查结果及四份安全通告。
- 研究人员将十一项发现中的六项评为高危,包括套接字转发类问题。Tailscale 的 TS-2026-014 至 TS-2026-017 通告覆盖已确认的 SSH 和 Serve 问题;截至 10 月 28 日,未列出 CVE 标识符或 CVSS 评分。利用仅在实验室演示:测得受策略边界约束的自动传播,每跳不到一秒,无需密码或被盗密钥。没有已知在野攻击。
- 立即在 tailscaled 服务环境中设置
TS_SSH_DISABLE_FORWARDING=1并重启服务,将 SSH 规则改写为逐对授权,并将客户端升级至 1.104.4 或更高版本。headscale 服务器发现仍需单独处理:更新 Tailscale 客户端不会修复 headscale 控制服务器问题。
报告按以下方式编号各项发现;严重性为研究人员的评估:
| ID | 严重性 / 分类 | 组件 | 影响 |
|---|---|---|---|
| 1 | 高危 | SSH 本地转发 | 以 root 连接特权套接字。 |
| 2 | 高危 | SSH 远程转发 | 替换 root 拥有的套接字。 |
| 3 | 高危 | SSH 路径验证 | 竞态绕过允许列表。 |
| 4 | 高危 | 实验性 conn25 | 已授权用户选择未经授权的出站目标。 |
| 5 | 高危 | gliderssh 会话 | 耗尽守护进程内存。 |
| 6 | 中危至高危 | headscale / DERP 客户端 | 注册拒绝服务;内部人员或恶意中继导致内存耗尽。 |
| 7 | 高危 | Serve TCP 转发 | 暴露特权本地 API。 |
| 8 | 中危(影响受限) | Serve Unix 后端 | 符号链接交换重定向特权连接。 |
| 9 | 中危(威胁模型发现) | Funnel | 本地管理员绕过 CLI 属性检查。 |
| 10 | 配置说明 | Serve 接口绑定 | 显式启用的设置将监听器暴露到隧道之外。 |
| 11 | 使用请求头授权时为中危;否则为低危至中危 | Serve HTTP/2 | 能力请求头跨越挂载边界。 |
Tailscale 的评估区分了漏洞和预期行为:发现 9 和 10 按设计工作,发现 4 涉及尚未用于生产的 conn25,而非稳定版应用连接器。
我是否受影响?
Tailscale SSH 套接字转发提权。 对于未修补客户端,如果以下条件全部成立,您就受影响:
- 您在目标节点运行 Tailscale 客户端,并启用了 Tailscale SSH。
- SSH 策略允许连接用户或设备到达目标,且转发已启用,无论通过
accept规则还是获准的check会话。headscale 0.29.5 对接受的会话授予转发能力,没有逐规则禁用开关。 - 连接设备已失陷,或连接用户就是攻击者:这些发现可从完全无特权账户触发,无需目标上的密码、密钥或 sudo。
headscale 控制服务器拒绝服务(发现 6)。 可从互联网访问的 headscale 0.29.5 注册端点,无需账户或 SSH 访问即可被攻击。报告单独说明,内嵌 DERP 内存耗尽需要已通过身份验证的内部人员;而 Tailscale 客户端响应体攻击的前提是恶意中继。
| 组件 | 测试版本 | 修复状态 |
|---|---|---|
| tailscaled,包括内嵌的 Tailscale SSH 服务器、serve/funnel 和 DERP 中继客户端 | 1.104.2 | 1.104.4 修复发现 1–3、5、7、8、11 及发现 6 的客户端部分;conn25 和 headscale 需要单独评估 |
| headscale 控制服务器 | 0.29.5 | 报告 10 月 28 日状态中,服务端修复仍待完成;注册缓存重构由 PR #3586 跟踪 |
| gliderssh 库 | Tailscale 1.104.2 内置的 v0.3.4 快照 | 会话耗尽修复随 Tailscale 1.104.4 发布(PR #21945 和 #21953) |
检查运行版本及策略允许的范围:
tailscale version # client version on every node
tailscale status # the peers this device can see
headscale version # self-hosted control server, if you run one
sudo systemctl show tailscaled --property=Environment # is TS_SSH_DISABLE_FORWARDING set?
审计报告称,在其测试环境中,节点可以枚举 tailnet 对等节点。不要依赖策略保密:检查策略文件中的 SSH 规则,SaaS 部署在 Tailscale 管理控制台中查看,headscale 则查看其策略文件;统计有多少规则是针对整个身份或整个标签的 accept 规则,而不是一个源设备到一个目标设备。
如何修复?
将 Tailscale 客户端升级至 1.104.4 或更高版本。在安排更新期间,降低转发暴露并收紧 SSH 访问;headscale 控制服务器加固仍是独立任务。
- 除非有明确记录的需求,否则禁用 SSH 转发。在 tailscaled 服务环境中设置:
sudo mkdir -p /etc/systemd/system/tailscaled.service.d &&
printf '[Service]\nEnvironment=TS_SSH_DISABLE_FORWARDING=1\n' |
sudo tee /etc/systemd/system/tailscaled.service.d/disable-forwarding.conf &&
sudo systemctl daemon-reload &&
sudo systemctl restart tailscaled
Tailscale 的 10 月 26 日更新表示,这“可缓解几项更严重的发现”,而无需完全禁用 Tailscale SSH。在 SaaS 控制平面上,还可以将每条规则的转发字段设为拒绝;headscale 0.29.5 没有等效的逐规则开关。服务环境设置需在每个 Tailscale 客户端应用。
- 将 SSH 规则改写为逐对授权:特定源到特定目标,绝不使用覆盖整个身份或整个标签的 accept 规则,也不授予特权目标账户;将 SSH 限制到非特权账户仅是纵深防御,因为这些发现可从完全无特权账户触发。
- 在每个客户端安装 Tailscale 1.104.4 或更高版本,然后用
tailscale version验证。Tailscale 的 10 月 28 日调查结果和修复链接了 TS-2026-014 至 TS-2026-017 通告。 - 对于自托管 headscale 服务器,在修复版本发布前应用报告中的加固措施:systemd
MemoryMax、重启监督、注册端点前置限流,以及不暴露到开放互联网。 - 如果运行 tailscale serve,移除任何以 Unix 套接字为目标的 TCP 转发(
serve --tcp <port> unix:<path>)。 - 如果测试实验性 conn25,从不需要它的客户端移除该能力;在 Kubernetes 中,对连接器命名空间应用默认拒绝的出站 NetworkPolicies;并在连接器主机上实施内核层目标允许列表,报告提供了相应脚本。Tailscale 表示稳定版应用连接器不受影响。
根本原因是什么?
Tailscale SSH 的转发操作使用 root 守护进程凭据执行,因此每个试图约束它们的路径检查,都成为无特权会话可以赢得的竞态。
两个设计决策组合导致了问题。首先,Tailscale SSH 不执行用户级身份验证:身份验证使用方法 none 完成,因为 WireGuard 层已经识别设备,策略评估的是设备身份,而非用户凭据,因此会话在整个生命周期内携带设备的完整授权。
其次,内嵌 SSH 服务器位于以 root 运行的 tailscaled 内,其 gliderssh 库也以 root 执行 Unix 套接字转发操作:ssh -L 的连接,以及 ssh -R 的绑定和删除链接。
该库确实试图限制转发目标位置。在 tailscale/gliderssh 的 streamlocal.go 中,validateAndResolveSocketPath 和 validateAndResolveListenPath 函数在操作前将路径与目录允许列表比对,并解析符号链接,因为最终连接由内核而非验证器执行:
// validateAndResolveSocketPath validates socketPath lexically, then resolves
// its symlinks and re-checks that the real destination is still within
// opts.AllowedDirectories and not excluded by opts.DeniedPrefixes. It returns
// the resolved path to dial.
//
// The lexical check alone is insufficient because the kernel connects to the
// symlink target: a symlink inside an allowed directory could otherwise
// redirect the connection to a socket outside it (e.g. a link in the user's
// home dir pointing at /var/run/docker.sock).
根据 go.mod,Tailscale 1.104.2 内置 gliderssh v0.3.4-0.20260716005906-1a0f895faf28,允许列表覆盖用户主目录、/tmp 和 /run/user/<uid>。但验证发生在检查时刻,删除链接、绑定和连接发生在之后,届时内核会重新解析路径。
发现 3 展示了这一缺口:攻击者在自身 SSH 会话内运行 rename(2) 竞态循环,测得每秒约 170,000 次路径状态切换,让检查看到无害路径,再在 root 守护进程操作前将目录切换到目标。报告的一句话结论是:{home, /tmp, /run/user} 允许列表“不是安全边界”。
这一类问题已有 CVE 先例,也有已知解决办法。Dropbear 的 CVE-2025-14282 是连接侧的同类问题,在 2025.89 中通过在任何转发操作之前 setuid() 到会话用户而修复,从而无需路径检查。OpenSSH 是报告审计的清晰反例:其身份验证后的子进程在进入服务器循环前降权到会话用户,因此没有检查阶段,也没有检查到使用之间的间隙。
如何利用?
最简单的演示是发现 1,只需一条允许本地转发的 SSH 策略规则;它无需任何竞态就展示了 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'
连接由 tailscaled 执行,因此套接字看到的对端凭据属于 root。验证结果是 uid=0(root) 以及 /etc/shadow 内容,无需 sudo,也无需属于 Docker 组。该演示通过 tailscaled 使用目标原装 dockerd,不需要植入守护进程或另一个 dockerd 漏洞。最强利用链,即发现 3,利用检查到使用之间的竞态:
- 攻击者在任何策略允许的设备上取得立足点,来源可以是被利用的 Web 应用、恶意依赖、钓鱼植入物或被盗身份凭据,然后依据允许转发的规则,通过 Tailscale SSH 连接目标。
- 在会话内,攻击者对允许列表内的目录运行 rename(2) 竞态循环,每秒约切换 170,000 次路径状态,使词法检查看到无害路径,而内核在操作时重新解析到目标。
- root 守护进程删除真实路径上正在使用、属于 root 的套接字,例如
/run/docker.sock、tailscaled localapi 套接字或 headscale 控制套接字,并将攻击者的转发监听器绑定到该处。 - 攻击者保持重新绑定开放,经验证可持续 4 分钟或更久,并对服务实施中间人攻击。针对 localapi 套接字,捕获流量包括逐字节完整的
POST /localapi/v0/start,其中包含 authkey,即注册新设备的凭据。 - 攻击者在临时节点使用捕获的 authkey,以受害者身份将其加入 tailnet,形成一个看起来像合法带标签服务器、且路由自动获批的恶意节点;此流程已在实验室端到端演示。
一个附带发现扩展了这些原语:删除链接的检查关卡和监听后的 chmod 在使用时跟随词法路径,因此竞态会话可让 root 删除任意普通文件,并将其 chmod 为 0600;这虽不能修改内容,却能造成破坏。
攻击流程是什么?
一个拥有获准 SSH 规则的失陷设备,可以在目标服务器取得 root,继而成为网络上的持久节点,整条链无需盗取密码或密钥。
设备上取得立足点"] --> B["Tailscale SSH 会话
身份验证方法 none"] B --> C["ssh -R/-L Unix 套接字
转发请求"] C --> D["root tailscaled 在检查时
进行路径词法验证"] D --> E["rename 竞态在检查与
使用之间切换路径"] E --> F["替换 root 拥有的套接字
或以 root 连接它"] F --> G["root 代码执行及
注册密钥捕获"] G --> H["恶意节点加入
tailnet"] style D fill:#f8d7da style E fill:#f8d7da
攻击者能做什么?
套接字接管可触达任何使用 Unix 套接字的特权服务。报告已演示并留有证据的结果包括:
- 通过对原装 dockerd 的竞态执行 root 命令:一次赢得竞态的 keep-alive 连接完成了容器创建,将
/绑定挂载到容器内,并以 uid 0 执行命令。 - 通过 systemd 控制套接字获得 root shell:使用 root 凭据连接
/run/systemd/private被 systemd 授权,重放的 D-Bus 字节启动临时单元,产生 root 反向 shell,竞态投递间歇成功。对 kubelet 套接字桩的竞态连接也都以 uid 0 被接受。 - 设备注册凭据窃取:从套接字接管到注册的利用链,将重新绑定转化为路由自动获批的恶意节点;这种持久化可在原始主机重装后继续存在。此外,还可任意删除 root 文件并强制 chmod 0600,破坏可用性。
- 整个设备群传播:在策略授予的 SSH 路径下,报告测得自动传播可到达策略允许的每个设备,无需密码、被盗密钥或用户交互,每跳不到一秒。两个结构性限制有效:逐对访问授权阻止传播,硬件绑定的设备凭据阻止身份盗用持续存在。
- root 守护进程内存耗尽,发现 5:反复窗口变更和无限追加环境变量,在 7 秒内分配约 536 MB,断开连接后仍保留;该缺陷继承自上游 gliderlabs/ssh v0.3.8。
- 应用连接器出站验证,发现 4:实验性 conn25 让普通的单应用用户授权可以通过连接器选择任意出站目标,包括云元数据和 Kubernetes 服务,因为客户端声明的目标从未与应用配置的后端核验。Tailscale 表示这不影响稳定版应用连接器。
- headscale 拒绝服务,发现 6:未经身份验证的注册缓存驱逐可阻断整个组织的新设备接入;内嵌 DERP 内存耗尽需要已通过身份验证的内部人员。Tailscale 客户端子发现需要恶意中继。
对于套接字转发,条件是获准或已失陷的设备拥有策略允许的 SSH 访问,且未修补客户端上启用了转发。headscale 0.29.5 授予转发能力,却没有逐规则禁用开关。Check 模式,即 Tailscale 为 SSH 提供的浏览器重新身份验证,不限制转发:它批准会话建立,随后套接字转发发现便在获准会话整个生命周期内有效。
修复为什么有效?
发现 1–3 的 SSH 转发修复让套接字操作以会话用户身份运行,采用了报告建议、且先前用于 Dropbear CVE-2025-14282 修复的权限分离。 Serve 和资源耗尽发现有各自修复。根据报告的上游回应部分和 Tailscale 安全通告:
- 发现 1 至 3:SSH 转发重新设计为每连接监控进程,加上具备相应凭据的无特权子进程。转发套接字的连接、绑定和删除链接现在使用已认证会话用户的凭据执行,载荷对端凭据标识该用户,而非 root 守护进程。被竞态击败的词法路径重新验证被移除,改用内核自主访问控制作为边界。
- 发现 5:gliderssh 中的窗口变更发送变为非阻塞,并采用保留最新值的语义,环境变量反复变更也受到限制(PR #21945 和 #21953;上游提交信息致谢审计测量结果)。
- 发现 7:在配置时和每次连接时,拒绝 serve TCP 转发到 localapi 套接字(PR #21966)。
- 发现 8:每次连接时重新验证 Serve HTTP 代理的 Unix 后端目标,替代此前仅在重新加载配置时进行的符号链接检查。Tailscale 在 TS-2026-016 中确认此修复,随 1.104.4 发布。
- 发现 11:能力处理移至每请求上下文(PR #21931)。
- 发现 6(客户端):中继限制响应体读取大小(PR #21980)。
对于 SSH 转发,一旦连接、绑定和删除链接以会话用户身份运行,内核自身权限检查便决定会话可触及的对象;由于不需要特权检查,也就不存在检查到使用之间的间隙。Tailscale 1.104.4 于 10 月 27 日发布这些客户端修复,厂商于 10 月 28 日确认可用。报告测试的是 headscale 0.29.5,仍将其服务端修复列为待完成。更新 Tailscale 客户端不会修复这些 headscale 发现。
如何检查是否已遭入侵?
没有已知真实攻击。报告针对未修补系统调查的检测指导如下:
- 套接字生命周期审计:在允许转发的目录
/tmp、用户主目录和/run/user,以及代理自身的套接字路径上,为 Unix 套接字删除链接、绑定和 chmod 操作设置 auditd 规则;按路径和操作限定对象范围,而非仅按系统调用名称。在本应为 0600 的路径上出现模式 0755 的套接字,是竞态获胜后绑定、且监听后 chmod 被重定向的取证特征。 - 重命名风暴检测:竞态需要每秒约 100,000 次量级的持续目录重命名循环,因此在候选目录上设置带速率告警的 inotify 或审计监视可捕获它。
- 注册和密钥监控:对变更窗口之外签发或使用 authkey 发出告警;对使用此前未见密钥、以带标签身份加入的任何节点发出告警;将用户报告未经请求的 Tailscale 身份验证提示视为事件,因为攻击者连接可以触发 check 提示。对于 headscale,监控常驻内存、注册缓存反复变更和注册延迟。
如果发现失陷证据,重装镜像不等于遏制:节点身份是存储在状态文件中的密钥材料,拥有 root 的攻击者可能已将其复制到其他机器,在控制平面删除设备或密钥过期之前,它仍然有效。删除设备,轮换受影响主机可访问的任何 authkey 和凭据;如果签名节点失陷,还应轮换 Tailnet Lock。
时间线
- 2026-10-22 至 2026-10-24:Cyberdione Labs 测试已发布的 Tailscale 1.104.2、headscale 0.29.5 和内嵌 gliderssh 库;连接器发现涉及尚未用于生产的实验性 conn25。
- 2026-10-25:Tailworm 报告发布,包含十一项发现及复现工具包;The Hacker News 当天报道。
- 2026-10-26:Tailscale 发布“Tailscale SSH:调查与修复更新”,确认存在超出所配置本地用户权限的严重提权,并计划在测试通过的前提下,于 10 月 27 日结束前发布修补客户端。发现 1 至 3 和 5 的修复进入 tailscale 和 gliderssh 主分支。
- 2026-10-27:发现 7、11 和客户端中继组件的修复合并(PR #21966、#21931、#21980);headscale 注册缓存重构 PR #3586 仍待完成。Tailscale 将修补客户端标记为 1.104.4。
- 2026-10-27:本文发布。
- 2026-10-28:Tailscale 发布调查结果和修复,以及 TS-2026-014 至 TS-2026-017 通告;本文更新,反映 1.104.4 已可用及仍需处理的范围。
常见问题
Tailscale 要求所有人关闭 Tailscale SSH 吗?
不。Tailscale 的 10 月 26 日更新表示,“不建议所有人禁用 Tailscale SSH”,因为利用取决于授予连接用户或设备的权限,以及系统配置。对于未修补客户端,Tailscale 的临时步骤是禁用转发、审查 SSH 访问策略,并收紧敏感系统访问。其 10 月 28 日指导是升级至 1.104.4 或更高版本。
Check 模式能保护我吗?
不能。Check 模式仅控制会话建立;端口转发发生在已建立、获准的会话内,所有套接字转发发现会在该会话整个生命周期内适用。报告还测得,在 SaaS 控制平面上,一次成功 check 的授权在批准窗口过期前适用于所有 check 模式目标,因此用户自己的常规授权也可能使攻击者关注的目标变为可利用。
Tailnet Lock 能阻止恶意节点注册吗?
它提高了门槛,但在实验室中没有完全阻断。Tailnet Lock 默认关闭,此时从捕获 authkey 到恶意节点上线的完整利用链端到端成功;开启锁后,禁用秘密和签名密钥材料在传输中被捕获,不过并未实际执行禁用。签名节点失陷时,应轮换锁,而非仅做清理。报告为每项发现记录了复现工具包,没有已知针对真实部署的可用利用。
能从互联网利用吗?
SSH 发现不能直接从互联网利用:攻击者需要在策略已经允许的设备上取得立足点,或拥有获准账户。在报告实验室中,网状网络将一个立足点以机器速度转化为对策略允许目标的特权访问。headscale 发现不同:未经身份验证的互联网用户,可通过可从互联网访问的注册端点扰乱新设备接入,这就是不应暴露此类端点的原因。
哪个客户端版本修复这些问题?
升级至 Tailscale 1.104.4 或更高版本,并在每个节点用 tailscale version 确认。Tailscale 的 10 月 28 日调查与修复文章及 TS-2026-014 至 TS-2026-017 通告确认了该版本。在每个客户端更新前,禁用 SSH 转发并采用逐对 SSH 授权。headscale 控制服务器修复仍需单独处理。
使用 Ostorlab 扫描外部攻击面,找出暴露于互联网的 headscale 控制服务器,以及其他本不应从开放互联网访问的自托管基础设施。
参考资料
- Cyberdione Labs,“Tailworm:安全评估报告,Tailscale v1.104.2 和 headscale v0.29.5”,2026 年 10 月 25 日发布,含十一项发现、已验证复现、蠕虫式传播测量和检测指导,缓解措施矩阵于 10 月 28 日更新:https://tailworm.com/ (核查于 2026 年 10 月 28 日)
- Tailscale,“Tailscale SSH:调查与修复更新”,2026 年 10 月 26 日,确认发现、
TS_SSH_DISABLE_FORWARDING=1缓解措施及 10 月 27 日结束前发布修补客户端的目标:https://tailscale.com/ssh-investigation-update (核查于 2026 年 10 月 27 日) - tailscale/tailscale 修复拉取请求:#21941、#21942、#21943、#21944、#21945、#21953、#21959、#21963、#21966、#21931、#21980 (核查于 2026 年 10 月 28 日)
- tailscale/gliderssh
streamlocal.go,函数validateAndResolveSocketPath和validateAndResolveListenPath,即被竞态击败的先词法检查、后解析的验证:https://github.com/tailscale/gliderssh/blob/master/streamlocal.go (核查于 2026 年 10 月 27 日) - Tailscale 1.104.2
go.mod,将 gliderssh 固定为 v0.3.4-0.20260716005906-1a0f895faf28:https://github.com/tailscale/tailscale/blob/v1.104.2/go.mod (核查于 2026 年 10 月 27 日) - Dropbear CVE-2025-14282,同类先例,在 2025.89 中通过在任何通道或全局请求前设置会话 uid 修复:https://nvd.nist.gov/vuln/detail/CVE-2025-14282 (通过 Tailworm 报告引用,核查于 2026 年 10 月 27 日)
- headscale 注册缓存重构 PR #3586,采用准入控制替代驱逐,并引用报告:https://github.com/juanfont/headscale/pull/3586 (通过 Tailworm 上游回应引用,核查于 2026 年 10 月 27 日)
- The Hacker News 对 Tailworm 发现的报道,2026 年 10 月 25 日:https://thehackernews.com/2026/10/unpatched-tailscale-ssh-flaws-could-let.html (核查于 2026 年 10 月 27 日)
- Tailscale 1.104.4 发布:https://github.com/tailscale/tailscale/releases/tag/v1.104.4; 厂商调查结果和修复,2026 年 10 月 28 日:https://tailscale.com/blog/tailscale-ssh-findings-fixes; 安全通告 TS-2026-014 至 TS-2026-017:https://tailscale.com/security-bulletins (核查于 2026 年 10 月 28 日)