技術約13分で読めます

Tailscale SSHのUnixソケット転送からroot権限を奪える問題、Tailwormレポートと対策

いけさん目次

TL;DR

影響 Tailscale SSHを有効にしたノード(接続先サーバー)で、SSH接続を許可された利用者が、Unixソケットの転送機能を使ってroot専用のソケットへ接続できる。レポートではDockerやsystemdのソケットを経由したroot権限奪取を実証した。ほかにアプリコネクター(App Connectors)や tailscale serve、headscaleなどの問題を含めて11件

対応 2026年10月26日時点で修正版、CVE、Tailscaleのセキュリティ告知はどれも出ていない。Tailscaleは告知と修正版を順次出す方針を明らかにしている

条件 Tailscale SSH(tailscale set --ssh で有効にするSSHサーバー)を使っている場合のみ。OpenSSHなど通常の sshd にTailscale経由で接続している構成は、今回のソケット転送問題の対象外


2026年10月25日、Cyberdione Labsのエリカ・ウィンディッシュ(Erica Windisch)氏が、Tailscaleの脆弱性をまとめたレポート「Tailworm」を公開した。
Tailscaleのクライアント(tailscaled)v1.104.2と、管理サーバーのオープンソース実装headscale v0.29.5を対象に、10月22日から24日にかけて研究者の検証環境で再現を確かめた内容だ。

このレポートは、ベンダーとの日程調整を経ないまま公開された。
ウィンディッシュ氏自身もHacker Newsで「報告はしたが、日程調整はしていない」と述べている(Hacker News)。
事前調整のないゼロデイ公開となったため修正版のリリースやCVE番号はまだなく、利用者が設定で防ぐしかない状態になっている。

Tailscale SSHはrootで動くSSHサーバー

Tailscaleは端末間で暗号化通信を行うメッシュVPNだが、各端末上で動く常駐プログラム tailscaled は、ネットワーク設定を変更する必要上、root権限で動作している。

Tailscale SSHは、このネットワーク上でのSSH接続をTailscale自身が受け付ける機能で、ノードごとに tailscale set --ssh を実行して有効にする(Tailscale SSH)。
有効にすると、外部から来たポート22番への接続は通常の sshd ではなく、tailscaled 内に組み込まれた独自のSSHサーバー(Goのgliderlabs/sshを改造したgliderssh)が受ける。つまりSSHサーバー自体が tailscaled と同じroot権限のプロセスの中で動く。

接続可否はパスワードや公開鍵ではなく、tailnetのアクセスポリシーで決まる。
通信が届いた時点で接続元の端末やユーザーは確定しているため、「この端末からこのアカウントへ入ってよい」というルールに合致すれば、SSH層で改めてパスワードや鍵を求められることなくログインできる。
問題は、こうしてポリシー経由で正当に入った一般ユーザーのセッションが、なぜ接続先サーバーのroot権限まで奪えてしまうのか、という点だ。その原因が、次節のUnixソケット転送の処理にある。

Unixソケット転送がroot権限で処理される問題(所見1〜3)

SSHには、接続先マシンのポートやUnixソケットを手元へ中継するローカル転送(ssh -L)と、逆に接続先へ受け口を作るリモート転送(ssh -R)がある。
Unixソケットは同一マシン内のプロセス間通信に使われ、Dockerの /run/docker.sock やsystemdの /run/systemd/private のように、root専用で保護されているものが多い。たとえばDockerソケットを叩ければ、root権限のコンテナを動かしてホスト全体のroot権限を握れる。

OpenSSHでは、ログイン完了時にサーバー側プロセスがログインしたユーザーの権限へ切り替わり、そのユーザーが開けるソケットだけを転送する。
ところがTailscale SSHでは、転送処理を tailscaled 自身、つまりroot権限のプロセスが行う。

v1.104.2のコードでは、転送先として指定できるパスを、ログインユーザーのホームディレクトリ、/tmp、/run/user/<uid> の3か所に制限している(tailssh.go)。
レポートの所見1〜3は、この3か所の制限があっても、プロセスがrootで動いているためにroot権限のソケットへ到達できる3つの経路を示している。

  1. 接続元がroot扱いになる(所見1)
    ssh -L でソケットへ接続する際、ソケット側のプロセスからは接続元がrootに見える。レポートでは /tmp に配置したDockerソケット経由でコンテナを起動し、ホスト側の uid=0 と /etc/shadow(パスワードハッシュ)の内容を取得した。
  2. 既存ソケットを削除して置き換える(所見2)
    ssh -R で受け口を作成する際、指定パスにすでにroot所有のソケットが存在していても、それを削除して上書きしてしまう。本物のサービスになりすまして、そこへ送られてくるコマンドや認証情報を横取りできる。
  3. 検査直後のすり替え(所見3、TOCTOU競合)
    パスが許可された場所にあるかを検査してから実際にファイルを操作するまでのごくわずかな隙に、シンボリックリンクで行き先を差し替える。この競合により3か所の制限をすり抜け、本来届かないはずの /run 以下のソケットへ接続できる。

シンボリックリンクを使った制限回避自体は、2026年6月3日の告知TS-2026-004でも修正されていた(Security Bulletins)。
以前のTailscale SSHはパスの文字列だけで判定していたため、ホームディレクトリに /var/run/docker.sock へのシンボリックリンクを置くだけでDockerソケットに接続できた。v1.98.9でリンク先まで検証するよう修正されたが、所見3はこの「検査した直後」を突く。
検査時点では /tmp 以下の無害なパスに見えても、操作直前に別ファイルへ差し替えられると検査をすり抜けてしまう。レポートでは毎秒約17万回パスを切り替え続け、標準の場所にある /run/docker.sock 経由のコンテナ起動(uid=0 実行)と、/run/systemd/private 経由でのrootシェル取得を実証した。

検証環境における制約も報告されている。Docker経由の検証では、SELinuxが有効な環境だとホスト側ファイルの読み取りがSELinuxポリシーで拒否され、コンテナの起動・実行までにとどまった。またsystemd経由の競合成功率は0〜4%と低く、常に成功するわけではない。

このソケット奪取から次のような横展開(ラテラルムーブメント)が成立する。

所見2の ssh -R で tailscaled の管理用ソケット(CLIとの通信口)を置き換えると、管理コマンドの通信を横取りできる。レポートではそこから新しい端末をtailnetに参加させるための認証キー(authkey)を取り出し、攻撃者のマシンを正規サーバーとしてtailnetへ参加させるところまで実証した。

flowchart TD
  A["Webアプリ脆弱性や改ざんパッケージ等で<br/>1台の端末に侵入"] --> B["ポリシーで許可された<br/>別サーバーへTailscale SSH"]
  B --> C["ssh -L / -R で<br/>Unixソケットを転送"]
  C --> D["tailscaledがrootで<br/>ソケットを開く・置き換える"]
  D --> E["Docker・systemd経由で<br/>root権限を取得"]
  D --> F["管理用ソケットの通信から<br/>authkeyを取得"]
  F --> G["攻撃者のマシンを<br/>tailnetに参加させる"]
  E --> B

いったん1台の端末に乗っ取りが入ると、tailnetのアクセスポリシーが許す範囲のサーバーへ次々にroot権限で侵入を広げられる。研究者の検証環境では、パスワードも盗んだSSH鍵もなしに、1台あたり1秒未満で感染が拡大したという。

なお、新端末の参加に既存端末の署名を求めるTailnet Lockを有効にしている場合について、レポートは通信の途中でロック解除に使う情報を取り出せたとしている。ただし、実際にロックを解除して端末を参加させるところまでは検証に含まれていない。

アプリコネクターやserveなど残る8件の所見

レポートが取り上げた全11件のうち、所見4以降の8件はTailscale SSHのソケット転送以外の問題だ。これらは特定の機能や実験的オプションを使っている場合のみ当てはまる。

著者のウィンディッシュ氏自身、利用者への影響が大きい順として「Tailscale SSHの権限昇格」「アプリコネクターの宛先制御」「tailscale serve の認可回避」の3つを挙げている(Hacker News)。残る8件の内容は次のとおりだ。

所見対象内容レポートの深刻度
4アプリコネクター(conn25、実験的機能)アプリの利用を許可されたユーザーが、コネクターを経由する通信の宛先を自由に指定できる。クラウドのメタデータサービス(認証情報を返す内部エンドポイント)や、Kubernetesクラスター内の内部サービスに到達できたHigh
5Tailscale SSHSSHセッションで画面サイズの変更や環境変数の送信を繰り返した際に、tailscaled のメモリが増え続けた。7秒で約536MBを確保し、切断後も100秒以上解放されなかったHigh
6headscale、DERPクライアント認証なしの登録要求を1024件送ると、承認待ち端末の登録情報が破棄される。悪意のある中継サーバー(DERP)に向けられたクライアントは、10MBの応答から1.34GBのメモリを確保させられるMedium-High
7tailscale serveserve --tcp で tailscaled 自身の管理用ソケットを公開する設定が通ってしまい、ほかの端末から設定を書き換えられる。管理者がこの設定をした場合のみHigh
8tailscale serve転送先ソケットの検査が設定時の1回のみで、あとからシンボリックリンクに差し替えるとroot専用のソケットへ接続できるMedium
9Funnelインターネットへ公開する機能の認可が、クライアント側の設定でのみ確認されているMedium
10tailscale serve環境変数 TS_SERVE_ALLOW_ALL_INTERFACES を設定すると、tailnet外のLANからも serve の公開先にアクセスできる。既定では無効注意
11tailscale serveHTTP/2で同じ接続に複数のリクエストを並行して送ると、別の公開パスに付与されるはずの権限情報ヘッダーが混ざる。レポートでは4000回中8回、許可されていないパスがHTTPステータス200を返したMedium

Tailscaleの声明と修正の状況

Tailscale側はどう対応しているのか。エンジニアリング担当副社長のマイク・シェイバー(Mike Shaver)氏は、10月25日にRedditで声明を出した(Recent Tailscale vulnerability reports)。

声明によると、Tailscaleはレポートの確認を進めるとともに、外部の専門家と社内で進めていた独自調査でも修正が必要な問題が見つかっていたという。接続許可後のUnix権限とSSH転送の扱いにおいて、ローカルユーザーに許される以上の操作がセッション内で可能になる場合がある、と説明している。
一方で、認証されていない攻撃者が任意のマシンへ接続できる問題とは性質が異なるとして、現時点の評価ではTailscale SSHを一律に無効化することまでは求めていない。確認できた所見、影響する設定、修正版をまとめたセキュリティ告知と詳細解説を後日出す方針だ。シェイバー氏はコメント欄で、本件がTailscale SSHを対象としたもので、通常の sshd にTailscale経由で接続する構成は含まれないことも認めている。

では、現時点で告知やリリースは出ているのか。
Tailscaleのセキュリティ告知一覧を確認すると、最新は10月21日のTS-2026-013(認証局eMudhraの中間証明書の秘密鍵流出)のままで、Tailwormに関する告知はない。GitHubのリリースも10月21日のv1.104.2にとどまる。

リリースはまだ打たれていないが、開発リポジトリの main ブランチを見に行くと、すでに修正コミットが入り始めている。
10月25日に入ったコミット566f89e0fは、同じTLS接続上のHTTP/2並行リクエストで権限ヘッダーが取り違えられる所見11の修正だ。権限情報を接続ごとの共有構造体ではなく、リクエストごとの構造体で保持するように改めている。同日のコミットafe142bc8(glidersshのタイムアウト競合修正)とともに、同じ社内イシュー(tailscale/corp#50380)を参照しており、社内では修正作業が進んでいることがうかがえる。

ただし、本丸であるUnixソケット転送をrootのまま処理する部分は、10月26日時点の main でも手付かずのままだ(tailssh.go)。レポートは、OpenSSHと同様に転送前にログインユーザーの権限へ切り替える修正(DropbearがCVE-2025-14282で取った手法)を推奨しているが、この根本修正はまだコード上に反映されていない。公式の修正版が出るまでは、利用者が手元で緩和策を取る必要がある。

headscaleではポリシーで転送を禁止できない

headscaleを管理サーバーに使っている環境では、公式サービスと異なりポリシー側での転送禁止ができない。
headscale v0.29.5のコードを見ると、SSHルールの action が accept であれば、ローカル転送もリモート転送も常に許可した設定をクライアントへ返してしまう(filter.go、同306〜308行)。
ポリシー記述で転送のみを拒否する方法がないため、headscale環境でSSHを許可したルールは、実質的にroot権限を渡すのと同等になってしまう。

headscaleは10月24日にv0.29.6を出しているが、eMudhra証明書への対応やGo依存ライブラリの更新が主で、Tailwormの修正は含まれていない(v0.29.6)。
また、headscaleでは全端末がネットワーク内の端末一覧を受信するため、1台侵入されれば攻撃対象がすべて判明する点も指摘されている。公式管理サーバーは未許可端末同士を一覧から除外しているため、これはheadscale固有の動作差とみられる。

修正版が出るまでの緩和策

Tailscale SSHを使っていなければ、所見1〜3と5は当てはまらない。
各ノードで tailscale debug prefs を実行し、RunSSH が false(または未設定)であれば対象外だ。

使っている場合にまず直感的に思いつくのは、「接続先のアカウントを一般ユーザーに絞る」対策だが、これは効果がない。所見1〜3は一般ユーザーのセッションから発生し、tailscaled がrootとしてソケットを中継してしまうためだ。

次にレポートや公式声明が推奨する「ポリシーで転送を禁止する」対策を検討したが、10月26日時点の公開ドキュメントのポリシー構文には、転送だけを制御する設定項目が見当たらない(Tailscale SSH)。また前述のとおり、headscale環境ではそもそもポリシーでの転送拒否が実装されていない。

管理サーバー側での制御が効かない中、ノード側で転送を遮断する手段として、tailscaled v1.104.2のソースコード内に環境変数 TS_SSH_DISABLE_FORWARDING が見つかった(tailssh.go)。
これを true に設定して起動された tailscaled は、ポリシーの設定内容にかかわらず、TCPポートとUnixソケットの転送をどちらも拒否する。ドキュメント未記載の隠し設定のため将来のバージョンで残る保証はないが、管理サーバーの設定に依存せず有効に機能するため、headscale環境でも適用できる。

systemdで動かしているLinux環境なら、sudo systemctl edit tailscaled で次の設定を書き、サービスを再起動すればよい。

[Service]
Environment=TS_SSH_DISABLE_FORWARDING=true

Tailscale SSHそのものが不要なノードであれば、tailscale set --ssh=false で無効化するのが確実だ。ただしそのノードへの唯一のアクセス手段がTailscale SSHである場合、無効化した瞬間に締め出されるため、事前に通常の sshd など別経路を確保しておく必要がある。

Tailscale SSH以外の機能についても、不要な経路は絞るのが基本になる。
tailscale serve status で unix: 形式のソケット(特に tailscaled.sock)へのTCP転送がないか確認し、アプリコネクターは不要なクライアントからの利用権限を外す。Kubernetes環境ならコネクターの名前空間に外向き通信を拒否する NetworkPolicy を適用しておく。headscaleはインターネットへ直接さらさず、systemdの MemoryMax でメモリ上限を設定しておくのが安全だ。

また、侵入された端末をOS再インストールだけで済ませると、持ち出されたノード鍵によって攻撃者のマシンから同一端末としてアクセスを継続されるリスクがある。怪しいノードは必ず管理側から端末ごと削除し、タグ付きサーバーであってもノード鍵の有効期限を設定しておく必要がある。

公式のセキュリティ告知や修正版タグはまだ出ていないが、main ブランチのコミットからわかるように、HTTP/2のヘッダー混線(所見11)やglidersshの競合修正などは順次進んでいる。本丸であるUnixソケット転送の権限分離パッチがどのように入るか、公式告知の公開を待ちたい。