盗まれたトークンにサーバーは要らなかった
Wiz Research が追跡した Gheist は、インフォスティーラーのログや過去のサプライチェーン漏洩から GitHub トークンを取り出し、各トークンが届くすべてのリポジトリに、シークレットを吐き出すワークフローを仕込む自動化キャンペーンだ。850 を超えるアカウントから 13,500 を超えるブランチが作られた。シークレットは GitHub 自身の API から出ていくので、ブロックすべき攻撃者のサーバーは存在しない。Bromure Agentic Coding では、スティーラーが開発者マシンで見つける GitHub トークンは、ほかのどこから使っても GitHub に拒否される代用値であり、マシン内からの書き込みはすべて Mac 上のダイアログを待つ。
Gheist にはコマンドサーバーがない。ペイロードを実行するのも、シークレットをログに書くのも、そのログをトークンの持ち主に渡すのも、すべて GitHub だ。
10 月 25 日、Wiz Research は 9 月 15 日から追跡してきたキャンペーン Gheist の分析を公開した。自動化ツールが盗まれた GitHub トークンを受け取り、そのトークンが届くリポジトリをすべて列挙し、リポジトリのシークレットを出力するワークフローを仕込む。Wiz は 850 を超える侵害アカウントから仕込まれた 13,500 を超えるブランチを回収しており、10 月 24 日の時点でもキャンペーンは続き、変化し続けていた。昨日は The Hacker News も報じている。
このツールに必要な入力は一つだけで、Wiz はその出どころも示している。トークンは、インフォスティーラーのログや、3 月の Trivy スキャナー侵害のような過去のサプライチェーン侵害で露出したトークンと一致した。Wiz がすべての被害者に向けた助言には、トークン所有者の端末にインフォスティーラーがいないか調べよ、という一文が入っている。
Gheist の仕組み
ツールはリポジトリごとに、ci-sync- にランダムな 6 文字を付けた名前のブランチを作り、ファイルを一つだけ、.github/workflows/sync-check.yml を ci: sync config というメッセージでコミットする。コミットには署名がなく、トークンの所有者の名前で記録される。API リクエストのユーザーエージェントは gheist で、これがキャンペーン名の由来になった。
push がワークフローを起動する。ジョブは環境変数と secrets コンテキスト全体を、どちらも base32 でエンコードして実行ログに出力する。Wiz はこれが通る理由をこう説明する。「GitHub がログ内のシークレット値をマスクするのは、それがそのままの形で現れたときだけだ」。base32 の出力は保存された値と一致しないので、何もマスクされない。リポジトリのデプロイ環境に対するマトリックスによって、環境スコープのシークレットまで引き出される。それは多くの場合、本番用のものだ。
その後ツールは GitHub API 経由でログをダウンロードし、数秒後に実行記録を削除する。Wiz の言葉では、「シークレットは GitHub 自身の API を通って出ていくので、ブロックすべき攻撃者のドメインも外向きの接続も存在しない」。
ツールは改良を重ねた。ある亜種は、9 月 28 日の最初の実行から数時間のうちに、6 分のタイムアウト、環境マトリックス、リポジトリごとのペイロード変更を加えた。10 月 19 日には、セルフホストランナーを狙うジョブが加わり、31 種類のトークン、クラウドインスタンスの認証情報、Kubernetes のサービスアカウントトークンを探すようになった。
GitHub の新ルールが止まる場所
GitHub は 7 月 28 日以降、公開リポジトリで悪意があると判定したワークフロー実行を、コラボレーターが承認するまで保留している。Wiz が確認した公開リポジトリ上の Gheist の実行は、すべて action_required で止まっていた。「この制御は機能し、悪意あるペイロードは一度も実行されなかった」。
この保留が効くのは github.com の公開リポジトリだけだ。プライベートリポジトリと GitHub Enterprise Server では、ci-sync-* の実行が完了してから削除されたことを示す監査ログと、組織のワークフローがそれらのブランチから AWS ロールを引き受けた CloudTrail の記録を Wiz は見つけた。
トークンは開発者のマシンから来る
Gheist のブランチにはどれも有効なトークンが要る。Wiz はそれらのトークンを、スティーラーのログと漏洩したビルドツールまでたどった。開発者のノート PC には、gh CLI のログイン、~/.git-credentials の個人アクセストークン、シェルプロファイルの GH_TOKEN がある。インフォスティーラーはそれらを一度にまとめてコピーする。コーディングエージェントの普及で、こうしたトークンはさらにありふれたものになった。ブランチを push し、プルリクエストを開くエージェントには、常に手の届くところにトークンが必要だからだ。
盗みは信頼しているツールから来ることもある。10 月 19 日、OVHcloud の開発者の GitHub アカウントを何か月も握っていた攻撃者が、OVHcloud CLI の 5 つのリリース、v0.11.0 から v0.15.0 までのバイナリを、OVHcloud CEO の Octave Klaba の言葉で言えば「ディスクからシークレットを取り出す」ビルドに差し替えた。BleepingComputer の報道によれば、macOS、Linux、Windows のビルドがすべて影響を受けた。Klaba の助言はこうだ。「影響を受けた場合は、ディスク上のシークレットをすべて変更してください」。
盗まれたトークンが、その日のうちに Gheist のようなキャンペーンに届く必要はない。スティーラーのログは売買され、トークンは誰かが失効させるまで使える。
同じトークンを Bromure マシンで
Bromure Agentic Coding は、Claude Code、Codex、Grok、Kimi、omp を Mac 上の Ubuntu VM で動かす。本物の認証情報はホストに残り、エージェントは代用値で作業する。そうしたマシンの一つで、GitHub トークンが Gheist の連鎖をどうたどるかを追ってみよう。
ディスク上のトークンは代用値
GitHub トークンをマシンの Credentials に追加する。起動時、Bromure は VM に偽物を書き込む。gh が期待する形の ghp_ と 36 文字で、GH_TOKEN、gh の設定、~/.git-credentials に入る。エージェントの git push や gh pr create はこれまでどおり動く。リクエストが VM から github.com へ出ていくとき、Mac 上のプロキシが偽物を本物のトークンに差し替える。差し替えるのはそのホスト宛てだけだ。
では、トロイの木馬化された CLI をその VM で実行してみる。エージェントがプロジェクトのインストール手順に従ったときと同じように。CLI はディスクを読むが、そこにある GitHub の値はすべて代用値だ。開発者から普段奪われるほかの戦利品も同じだ。~/.aws/config は偽のシークレットキーを渡すヘルパーを指しており、プロキシが Mac 上で本物のキーを使って AWS リクエストに署名し直す。VM には読み取れる SSH 秘密鍵がない。SSH_AUTH_SOCK の先はホスト上のエージェントで、VM の代わりに署名するが、鍵そのものは決して渡さない。
Gheist は代用値を使えない
Gheist のツールは攻撃者自身のインフラで動く。買ったトークンで api.github.com を呼び、リポジトリが見えることを期待する。Bromure の代用値なら、GitHub は 401 Bad credentials を返す。Bromure は Mac から出ないソルトを使って本物のトークンから代用値を導出しており、それがどの本物のトークンを表すかを知っているのは、その Mac のプロキシだけだ。ツールはリポジトリを一つも列挙できず、ブランチを作ることもない。
Gheist の手口はすべて、トークンが何に届くかを問うことにある。書き込まれた VM の外では、Bromure の代用値はどこにも届かない。
スティーラーへ向かう代用値は警報を鳴らす
スティーラーは見つけたものをどこかへ送らなければならない。Bromure の VM からは、HTTPS 接続はすべて Mac 上のプロキシを通る。プロキシは外向きのリクエストをヘッダーも本文もすべて調べ、発行先ではないホストへ向かう代用値を探す。GitHub 以外へ向かう ghp_ の代用値は、1 バイトも Mac を出る前に HTTP 451 でブロックされる。VM は一時停止し、This environment may have been compromised という警告が、認証情報の名前、それが属するホスト、送り先のホストとともに表示される。
マシンをシャットダウンすることも、調査のためにディスクとホームフォルダを保存することもできる。侵害済みとマークされたマシンは、ディスクとホームが消去されるまで再起動しないので、スティーラーが再起動を生き延びることはない。
マシンの Guardrails ペインで Unmatched traffic を Deny にし、プロジェクトに必要なホストだけを許可しておけば、スティーラーのアップロード先は、暗号化されたアーカイブであろうとなかろうと、そもそも拒否される。拒否された接続は Security Timeline の一行になる。
マシン内からの書き込みはあなたを待つ
マルウェアが盗みをあきらめ、代用値が機能する場所、つまり VM の内側からプロキシを通して使うとしよう。そのためには Gheist と同じこと、ブランチを作ってワークフローをコミットすることが必要になる。
新しい Bromure マシンは、GitHub の書き込みポリシーを Prompt before write に設定している。読み取りは通る。git push や github.com への REST の書き込みは、Mac 上のダイアログで止まり、操作がそのまま表示される。
Allow write on "GitHub" from workspace "web-app"?
POST /repos/<owner>/<repo>/git/refs
プルリクエストを見ている最中に現れた、頼んだ覚えのない ci-sync- ブランチなら、拒否するのは簡単だ。Don't allow は、ブロックするポリシーと同じエラーをエージェントに返す。Allow once が許すのは、その 1 回の書き込みだけだ。
ローテーションは一か所で済む
Wiz が挙げる最初の対応手順は、影響を受けたリポジトリが届きえたシークレットをすべてローテーションし、コミットを push したトークンを失効させることだ。「実行記録やブランチを削除しても、露出はなかったことにならない」。
Bromure の Mac では、このローテーションのうち開発者側の作業は入力欄一つで済む。本物の GitHub トークンは、ホスト上の暗号化された一つのエントリにある。新しいトークンをマシンの Credentials ペインに貼り付ければ、次の起動で対応する代用値が書き込まれる。シェルプロファイルや .env ファイル、エージェントの設定にあるコピーを探し回る必要はない。1Password の参照(Other API keys の下の op://vault/item/field)として保存したキーなら、Bromure が 2 分ごとに解決し直すので、1Password でのローテーションは再起動なしで実行中のマシンに届く。
保管庫そのものは、macOS の Data Protection Keychain にある鍵で守られている。この鍵は Bromure の署名 ID に紐づき、同期されることはない。Bromure のフォルダを Mac からコピーしたスティーラーが得るのは暗号文だけだ。
調査はエージェントに、読み取り専用で
Wiz の調査手順は、多数のリポジトリにまたがる地道な作業で、だからこそエージェント向きだ。^ci-sync-[0-9a-z]{6}$ に一致するブランチをすべて列挙し、ci: sync config というメッセージで sync-check.yml だけを追加しているコミットを見つけ、完了から 1 分以内に削除された実行と、ユーザーエージェント gheist を監査ログから抜き出す。
この仕事は、GitHub の書き込みポリシーを Read-only にした Bromure マシンに任せよう。エージェントはトークンが許すすべてのリポジトリと監査ログを読めるが、push や REST の書き込みは GitHub に届く前に Mac 上で拒否される。エージェントが中身を確かめるために ci-sync- ブランチをチェックアウトしても、Wiz が PolinRider のローダー付きで再 push されたのを確認したブランチであっても、そこで動くものはすべて代用値だらけの VM の中で動く。認証情報の差し替えはすべて Security Timeline に Credential brokering の行として残るので、調査がどのホストに触れたかを後から確認できる。
スティーラーが見つけるのは代用値
GH_TOKEN、gh の設定、~/.git-credentials にあるのは ghp_ の偽物だ。本物のトークンは Mac 上で暗号化されたまま残る。
Gheist には 401
マシンの外では、代用値は GitHub トークンではない。ツールはリポジトリを列挙できず、何も仕込めない。
漏洩は VM を止める
GitHub 以外へ向かう代用値は 451 でブロックされ、VM はあなたが扱いを決めるまで一時停止する。
書き込みは先に確認
新しいマシンは、GitHub への push と REST の書き込みのたびに、正確なリクエストを画面に出して確認する。調査は読み取り専用で走らせられる。
トークンをノート PC から外す
GitHub の承認保留は公開リポジトリでは機能したし、Wiz の堅牢化リストも正しい。本番環境での必須レビュアー、保護されたブランチに限定した OIDC の信頼、個人アクセストークンの代わりに GitHub Apps や短命のきめ細かいトークンを使うこと。どれも、誰かの手に渡った盗難トークンにできることを狭める。
Bromure Agentic Coding はその一歩手前で働く。エージェントは一日中ブランチを push し、プルリクエストを開くが、使っているトークンがスティーラーに読めるディスクに置かれることはない。Gheist がワークフローを仕込めるのはまだ有効なトークンだけで、Bromure マシン上のトークンが機能するのは、そのマシン自身のプロキシを通したときだけだ。
コーディングエージェントのファイルを狙って作られたスティーラーに対して同じ代用値がどう持ちこたえたかは「スティーラーは代用値を持ち去った」を、盗まれた GitHub トークン一つが汚染されたリリースに行き着いた例は「エージェントのトークンはオーナーのものだった」を読んでほしい。