← 海外の記事一覧へ

セキュリティノート

Gheist盗んだGitHubトークンでActionsのシークレットを回収

分類:攻撃手法認証情報の悪用攻撃手法サプライチェーン対象技術開発基盤・CI/CD

要点

影響バージョン

  • 特定の製品バージョンの脆弱性ではない。書き込み権限のある GitHub トークンを盗まれたアカウントがアクセスできるすべてのリポジトリが対象になる(Wiz)。
  • GitHub の仕様では、リポジトリへの書き込み権限がある人はそのリポジトリのシークレットをすべて読める。ワークフローを push できれば、permissions: {} を書いてもシークレットは渡る(Wiz、The Hacker News)。
  • 公開リポジトリでは、GitHub が2026年7月28日に始めた「怪しい実行を共同作業者の承認まで止める」仕組みで、Wiz が確認した Gheist の実行はすべて action_required で止まっていた(Wiz)。非公開リポジトリと GitHub Enterprise Server にはこの仕組みがない(Wiz、The Hacker News)。
  • self-hosted ランナーを使っている組織では、10月19日以降、ランナーのホスト上で認証情報を集めるジョブが追加された(Wiz)。

今すぐやること

  • 組織内のリポジトリで、正規表現 ^ci-sync-[0-9a-z]{6}$ に合うブランチと、.github/workflows/sync-check.yml を追加するコミット(メッセージ ci: sync config、署名なし)を探す(Wiz)。
  • 監査ログで、ユーザーエージェント gheist、ci-sync-* ブランチの実行が完了直後(1分以内)に削除された記録を探す。AWS CloudTrail では、OIDC のサブジェクトに ci-sync- を含むロールの引き受け(AssumeRoleWithWebIdentity)を探す(Wiz)。
  • 見つかったら、そのリポジトリ・組織・環境のシークレットをすべて入れ替え、push に使われたトークンを失効させ、持ち主の端末にインフォスティーラー(情報窃取型マルウェア)がいないか調べる(Wiz)。
  • self-hosted ランナーで .gdmp_pat・.gdmp_patg・.gdmp_sweep というファイルや、ログ中の ===== gdmp-pivot: が見つかったら、ランナーのホストとそのインスタンスロールを侵害されたものとして扱う(Wiz)。
  • 公開リポジトリで承認待ちになっている見覚えのない実行は、承認しない(Wiz)。

概要

  • Wiz の Rami McCarthy 氏と Hila Ramati 氏は10月25日、盗まれた GitHub トークンを大量に使う自動ツールを「Gheist」として公表した。2026年9月15日から追跡し、850超のアカウントから1万3500本超のブランチが作られた。10月24日時点でも活動は続いていた(Wiz、The Hacker News)。
  • 手口は単純で、ブランチを作ってシークレットをログに出すワークフローを置き、push で実行させ、ログを API で回収したら実行履歴を消す。外部への通信も C2 もないため、ネットワーク監視では見えない(Wiz)。
  • 公開リポジトリではほぼ止められた一方、非公開リポジトリと GitHub Enterprise Server では実行が完了し、組織のワークフローがそのブランチから AWS のロールを引き受けた記録も見つかった。Wiz はツールの不具合も多いとして、「大規模な悪用を試みたがつまずいた」と評している(Wiz)。

この記事では、ベンダー・公的機関・研究者・報道で確認できた内容を「事実」、筆者の推測や意見を「考察」として分けて書いています。

何が起きたか(時系列)

日付出来事
2026年3月Trivy の侵害。Wiz は、Gheist が使ったトークンの一部の入手元としてこの件とインフォスティーラーのログをたどれたとする(Wiz)
2026年5月別の攻撃「Megalodon」。のちに Gheist の被害アカウントのうち57件がこちらの被害にも遭っていたと判明(Wiz)
7月28日GitHub が、公開リポジトリで怪しいワークフロー実行を共同作業者の承認まで止める仕組みを開始(Wiz)
9月15日Wiz が Gheist の追跡を開始(Wiz)
9月28日2つ目のツール(ツールB)が timeout-minutes: 6 と、環境ごとのジョブ(マトリクス)を追加(Wiz)
10月6日同じアカウント群の一部、391アカウントが約1461のリポジトリを海賊版ソフトのおとりに改名し、610本の ci-sync ブランチが消える。このうち166アカウントは Gheist の活動には出てこない(Wiz)
10月9日GitHub が GhostAction の亜種のブロックを開始(The Hacker News、GitGuardian の情報として)
10月18日25アカウントが暗号資産の採掘用に「Windows RDP Runner」というリポジトリを作成(Wiz)
10月19日ツールBが出力に区切りを追加。数時間後、runs-on: self-hosted の sync-host ジョブを追加(Wiz)
10月19日〜20日北朝鮮との関連が指摘される PolinRider により、被害者3人のコミットが vite.config.js にローダーを足した形で push し直される(Wiz)
10月24日Wiz の確認時点で活動は継続中。アカウントの約3分の2は GitHub への報告などで消えていた(Wiz)
10月25日〜26日Wiz が公表(25日)、The Hacker News が報道(26日)(Wiz、The Hacker News)

技術的な問題点(根本原因)

事実(Wiz)

  • 手順。 ツールはトークンの持ち主名義で、ci-sync- に6文字を足したブランチを作り、.github/workflows/sync-check.yml を ci: sync config というメッセージでコミットする(署名なし)。push そのものがワークフローを起動する。
  • ワークフローの中身。 permissions: {} を指定し、ubuntu-latest で次の2つを実行する。
    • env | base32 -w 120(環境変数をすべて出力)
    • echo -n '${{ toJSON(secrets) }}' | base32 -w 120(すべてのシークレットを JSON にして出力)
  • マスクが効かない理由。 GitHub がログで伏せ字にするのは、シークレットの値がそのまま出たときだけ。base32 に変換した文字列は全文がログに残る。
  • permissions: {} が効かない理由。 これは GITHUB_TOKEN の権限を絞る設定で、secrets コンテキストの中身は制限しない。
  • 環境ごとのシークレット。 ツールBは、環境(environment)ごとにジョブを並べるマトリクスで、環境に限定したシークレットも出させた。
  • 後始末。 ログを API でダウンロードし、数秒後に実行履歴を削除する。
  • 2つのツール。 ツールAはブランチ名の末尾が16進数で、出力はそのまま。ツールBは末尾が36進数で、9月28日にタイムアウトと環境マトリクス、10月19日に区切り(echo X|base32 の結果 LAFA====)を加えた。
  • self-hosted ランナーへの拡張。 10月19日に追加された sync-host ジョブは、ランナーのホスト上で次のものを探す。
    • 31種類のトークン形式(Anthropic、Hugging Face、Groq、NVIDIA の鍵を含む)、秘密鍵、.env、Terraform の state、シェル履歴
    • /proc/*/environ(ほかのプロセスの環境変数)
    • Docker ソケット経由でホストのルートファイルシステムをマウント
    • AWS のインスタンスロールの認証情報、GCP・Azure のメタデータ
    • ランナー自身の認証情報ファイル、Kubernetes のサービスアカウントトークン 途中経過は ===== gdmp-pivot: という見出しでログに出し、.gdmp_pat・.gdmp_patg・.gdmp_sweep を書く。
  • 不具合。 このジョブは、ランナーの鍵ファイルの名前を間違えている、URL に埋め込まれた認証情報を探す正規表現が壊れている、GCP・Azure のトークンを取得していない、など不具合が多い。成功した実行は報告されていない。

事実(The Hacker News)

  • GitHub のドキュメントには「リポジトリへの書き込み権限を持つ人は、リポジトリに設定されたすべてのシークレットを読める」とある。承認まで止める仕組みは、現在 github.com の公開リポジトリだけが対象。

考察

  • 根本は脆弱性ではなく、「書き込み権限=シークレットを読む権限」という GitHub Actions の設計と、盗まれたトークンの組み合わせにある。ログのマスクは「うっかり出力」を防ぐ仕組みで、意図的な変換には効かない。
  • 外への通信がない手口は、送信先の IP やドメインで検知する GhostAction 型の対策をすり抜ける。ログそのものを持ち出し経路に使い、最後に実行履歴を消すため、被害組織は監査ログを見ない限り気づけない。

攻撃・侵入経路

flowchart TD
    A[インフォスティーラーのログ・Trivy侵害などで<br/>GitHubトークンが流出] --> B[Gheistがトークンで<br/>ci-sync-xxxxxxブランチを作成]
    B --> C[sync-check.ymlをコミット<br/>pushでワークフロー起動]
    C --> D{リポジトリの種類}
    D -->|公開リポジトリ| E[GitHubが承認待ちで停止<br/>action_required]
    D -->|非公開 / GHES| F[secretsとenvをbase32で<br/>ログに出力・マスクされない]
    F --> G[APIでログを回収<br/>数秒後に実行履歴を削除]
    F --> H[組織のOIDC設定が<br/>ブランチを制限していなければ<br/>AWSロールを引き受け]
    H --> I[数時間後、同じ組織のクラウド認証情報で<br/>AWS Secrets Managerを列挙]
    C --> J[self-hostedランナー上の<br/>sync-hostジョブ]
    J --> K[/proc・Dockerソケット・メタデータ・<br/>ランナー認証情報を探索]

事実(Wiz)

  • 企業の組織では、ツールが複数の利用者と自動化用アカウントのトークンを数分のうちに切り替えながら使い、まず非公開リポジトリをクローンした。数時間後には、同じ組織のクラウド認証情報で AWS Secrets Manager が列挙された。
  • CloudTrail には、組織自身のワークフローが Gheist のブランチから AWS のロールを引き受けた記録が残っていた。
  • 同じアカウント群は、海賊版ソフトのおとりへのリポジトリ改名(10月6日)、暗号資産の採掘(10月18日)、PolinRider によるコミットの書き換え(10月19日〜20日)など、別の悪用にも使われていた。
  • Gheist は GhostAction(10月11日の記事で扱ったもの)とは別物で、ワークフローのファイルも違う。GhostAction は 193.32.204[.]199 に送信する(Wiz、The Hacker News)。

考察

  • 1つの盗まれたトークンが、複数の攻撃者に売られたり共有されたりしていた可能性が高い。Gheist だけを止めても、同じトークンで別の悪用が続く。トークンの失効が最優先になる。

なぜ防げなかったか・構造的要因の考察

事実(Wiz、The Hacker News、GitHub のドキュメント)

  • 公開リポジトリの承認待ちの仕組みは効いたが、非公開リポジトリと GitHub Enterprise Server は対象外だった。
  • GitHub のドキュメントでは、OIDC トークンのサブジェクトにブランチ名が入るのは、ジョブが環境(environment)を使っていない場合に限られる(GitHub のドキュメント)。

考察

  • トークンの寿命と範囲。 開発者の端末に残る長期間有効な個人用トークン(classic PAT)は、インフォスティーラーに抜かれるとそのまま組織全体への書き込み権限になる。端末の保護と、トークンの有効期限・範囲の短縮が根本対策になる。
  • 「ブランチを push できる」ことの重さ。 多くの組織は、保護ブランチへのマージを守っていても、新しいブランチの作成とその上でのワークフロー実行は自由にしている。push で起動するワークフローは、レビュー前にシークレットに触れる。
  • OIDC の信頼ポリシーが広すぎる。 repo:org/repo:* のようにリポジトリ全体を信頼していると、攻撃者が作った一時ブランチからもクラウドのロールを取れる。Wiz が AWS のロール引き受けを確認したのは、まさにこの設定だったと考えられる。
  • 非公開だから安全という思い込み。 公開リポジトリの保護が強まった結果、攻撃の成果は非公開リポジトリと社内の GHES に集中した。守りが薄い側に攻撃が移るのは自然な流れである。
  • ツールの不具合に助けられた面。 self-hosted ランナー向けのジョブが正しく動いていれば、ホストのクラウド権限まで取られていた。次の版では直っている前提で備えるべきである。

運用者向けの具体的な対策と検知ポイント

事実(Wiz)

  • 検知に使える指標
    • ユーザーエージェント: gheist
    • ブランチ名: ^ci-sync-[0-9a-z]{6}$
    • ワークフロー: .github/workflows/sync-check.yml、コミットメッセージ ci: sync config
    • ワークフローファイルの blob SHA
      • f3da2b8b357dcda02fd455d314be2a95007516a6(出力そのまま)
      • d09535857a741bbb976bdc084a9a021713133291(タイムアウト付き)
      • 1dc5af9cce950aad1eb1546ecce8565b117540c4(区切り付き)
      • c665b778b2d822453293003e7ca0bbb2cb08e39d(self-hosted ジョブ付き)
    • self-hosted ランナー上: .gdmp_pat・.gdmp_patg・.gdmp_sweep、ログ中の ===== gdmp-pivot:、区切りの LAFA====
  • 監査ログで見るべき動き
    • ci-sync-* ブランチでのワークフロー実行が、完了から1分以内に削除されている
    • 1つのトークンや IP から、短時間に多数のリポジトリへブランチ作成・クローンが続く
    • CloudTrail で、サブジェクトに ci-sync- を含む OIDC のロール引き受け
  • 対応
    • リポジトリ・組織・環境のシークレットをすべて入れ替える。push したトークンを失効させ、持ち主の端末をインフォスティーラーの観点で調べる
    • self-hosted ランナーのホストとインスタンスロールを侵害されたものとして扱う
    • 承認待ちの実行を承認しない
  • 強化策
    • 環境(environment)に必須のレビュアーを設定し、本番用のシークレットは環境に置く
    • OIDC の信頼ポリシーを保護ブランチか環境に限定する
    • 長期間有効な個人用トークンをやめ、GitHub App か短期間の fine-grained トークンに移す
    • self-hosted ランナーは使い捨て(ephemeral)にし、Docker ソケットを渡さず、メタデータサービスへの到達を制限する

考察(運用上の工夫)

  • AWS の信頼ポリシーでは、token.actions.githubusercontent.com:sub を repo:org/repo:environment:production のように環境まで固定すると、レビュアーの承認なしにはロールを取れなくなる。ブランチで縛るなら ref:refs/heads/main に限定する。
  • 組織のルールセットで、ブランチ作成を許すパターンを決めておき、.github/workflows/ の変更を含む push を CODEOWNERS とルールセットで制限すると、push 起動の悪用面を減らせる。
  • GitHub の監査ログを SIEM に送り、「ワークフロー実行の削除」イベントを単独で警告にするのは費用対効果が高い。正規の運用で実行履歴を消す場面は少ない。

参考リンク

原文をGitHubで読む ↗

あわせて読む

ギフティ ― システムの脆弱性を突かれeギフトを不正発行された