CodeQLサプライチェーン攻撃の脅威 ― 公開されたGitHubシークレットから始まったセキュリティ事故
(praetorian.com)- 公開された GITHUB_TOKEN が短時間だけ有効になる隙を突くと、CodeQL を使う複数のリポジトリの GitHub Actions ワークフローでコード実行につながり得るサプライチェーン攻撃経路が開かれる可能性があった
- このトークンは
github/codeql-actionリポジトリの失敗したデバッグワークフローがアップロードした workflow artifact に含まれており、contents: write,actions: write,packages: write権限を持つ GitHub App インストールトークンだった - 攻撃の成否は、artifact のアップロード後からジョブ終了前までにトークンをダウンロードして使う レースコンディション にかかっており、実際のログ基準では約 1.022 秒、観測上は約 2 秒以内に branch 作成、
poc.txtの push、tag 作成が成功した - デフォルトの CodeQL 設定が内部的に
github/codeql-actionの v3 tag を実行し、その tag が不変ではなかったため、攻撃者が tag を悪意ある commit に移せば、デフォルトの CodeQL ワークフローを使うリポジトリまで影響を受ける可能性があった - GitHub は報告から約 3 時間後にデバッグ artifact アップロードを無効化する PR を出し、CVE-2025-24362 を割り当て、advisory でプラットフォームやシステム侵害の証拠はないと明らかにした
公開された artifact から始まる CodeQL サプライチェーン攻撃経路
- 公開された secret 1 つが GitHub CodeQL に対する潜在的な サプライチェーン攻撃 につながる可能性があった
- この secret は一度に約 1.022 秒 の間しか有効でなかったが、その間に攻撃者は GitHub Actions ワークフロー内部でコードを実行できる段階まで進めることができた
- 影響範囲は GitHub Cloud と GitHub Enterprise の両方に及び得た
- 想定される被害は次のとおりだった
- CodeQL を使用する private リポジトリの ソースコード流出
- CodeQL ワークフロー job 内の GitHub Actions secrets の窃取
- CodeQL ワークフローが動作する内部インフラ上でのコード実行
- CodeQL を使うリポジトリで GitHub Actions Cache を使うワークフローの secrets 窃取
- GitHub advisory によると、GitHub はプラットフォームやシステム侵害の証拠を確認していない
secret を見つけた方法
- GitHub Actions workflow artifact をダウンロードし、圧縮を再帰的に展開したうえで、Nosey Parker で secret をスキャンする Actions Artifacts Secret Scanner が使われた
- この機能は Chariot に統合され、Gato の機能としてオープンソース化された
- 1 日スキャンした後、
github/codeql-actionリポジトリの artifact からトークンが見つかった- 対象 artifact は「PR Check – Debug artifacts after failure」ワークフローがアップロードした
my-debug-artifactszip だった - 内部の
my-db-java-partial.zipを展開すると、crash report 内でghs_から始まる GitHub Token が検出された - 手動確認の結果、GitHub Runner の環境変数を含むファイル内に保存されていた GitHub App インストールトークンだった
- 対象 artifact は「PR Check – Debug artifacts after failure」ワークフローがアップロードした
GitHub Actions、artifact、CodeQL の接続構造
- GitHub Actions は YAML workflow に定義された処理を runner 上で実行する CI/CD プラットフォーム である
- すべての workflow run は自動生成される GitHub App インストールトークンである GITHUB_TOKEN を作成する
- runner はこのトークンで GitHub に認証し、workflow が要求する処理を実行する
- 権限は workflow ファイル、リポジトリ、組織レベルで設定できる
- GitHub Actions workflow artifact は、workflow が後で使うために GitHub Actions に保存するファイルである
- デフォルトでは、リポジトリの読み取り権限を持つ人がアクセスできる
- 最大 90 日間保存される
- CodeQL は GitHub のコード解析エンジンであり、GitHub リポジトリで静的解析を行って脆弱性を見つけるために使われる
- CodeQL のようなセキュリティツールは機密性の高いシステムやデータにアクセスする必要がある場合があり、侵害されると バックドア になり得る
トークン権限とレースコンディション
- ワークフローログの「Setup Job」段階で、当該 GITHUB_TOKEN がフル書き込み権限を持っていることが確認された
- 特に重要な権限は次のとおりだった
contents: write: branch 作成、tag 作成、release artifact アップロードが可能actions: write: Actions ジョブとworkflow_dispatchevent のトリガーが可能packages: write: package のアップロードが可能
- トークンは当該 workflow job の実行中のみ有効だったため、攻撃には 3 つの条件が必要だった
- トークンに write 権限があること
- job 完了前に artifact を取得できる upload artifact API v4 を使っていること
- artifact のアップロードと job 完了の間に、トークンのダウンロード、展開、使用に十分な時間があること
- CodeQL は
actions/upload-artifactaction を直接使わず、ソースコード内で upload artifact client を手動利用しており、コードコメント上では version 4 を使っていた - 生の GitHub ログ基準では時間間隔は非常に短かった
- 「Finalizing artifact upload」: 17:22:09.888
- 「Cleaning up orphan processes」: 17:22:10.911
- 計算上は約 1.022 秒 で、観測上は cleanup 後も約 1 秒トークンが有効だったため、約 2 秒とみなされた
PoC: artifact_racer.py の実行
- 検証用 Python スクリプト
artifact_racer.pyが作成された - 動作の流れは次のとおりだった
github/codeql-actionリポジトリで「PR Check – Debug artifacts after failure」ワークフローが開始されたかを継続的に問い合わせる- 実行中の workflow の artifact を監視する
- artifact が見えたらダウンロードし、GITHUB_TOKEN を抽出する
- 抽出したトークンで新しい branch を作成する
- その branch に
poc.txtという空ファイルを push する - その commit に新しい tag を作成する
- artifact のサイズが約 21MB だったため、制限時間内に成功する可能性があると判断された
- テスト用リポジトリで成功した後、
github/codeql-actionリポジトリに対して実行された - 約 2 時間後にワークフローが実行され、スクリプトはトークンを取得して branch 作成、file push、tag 作成に成功した
tag 改ざんが広げた影響範囲
- 攻撃者はそのトークンで保護されていない branch に悪意あるコードを追加できた
- よりステルス性の高い方法として、merge 前の feature branch に小さな悪意ある変更を入れ、merge を待つ戦略も可能だった
- tag 作成権限も重要だった
- 悪意あるコードを含む commit に
v3tag を付ければ、codeql-action@v3を手動で使う利用者が悪意あるコードを実行する可能性があった
- 悪意あるコードを含む commit に
- さらに大きな影響はデフォルトの CodeQL 設定から生じた
- リポジトリ設定で CodeQL をデフォルト設定として有効にすると、リポジトリの workflow 一覧には表示されない特別な GitHub Actions workflow が実行される
- この workflow はリポジトリの checkout、CodeQL 初期化、CodeQL scan、結果アップロードを行う
- 内部的には
github/codeql-actionリポジトリの action を v3 tag が指す commit として実行していた
- 当時の
v3tag は不変ではなく、GitHub が推奨する workflow pinning も使われていなかった - 攻撃者が侵害された GITHUB_TOKEN で
v3tag を悪意ある commit に移せば、デフォルトの CodeQL workflow を使うリポジトリが悪意ある CodeQL action を実行する可能性があった - CodeQL action は実行対象リポジトリのソースコードを checkout するため、悪意ある action は CodeQL デフォルト設定を使うリポジトリのソースコードを流出させ得た
GitHub Actions Cache poisoning 経路
- デフォルトの CodeQL action の GITHUB_TOKEN は read 権限しか持たないため、デフォルト設定だけでは repository write 操作や release バックドア、
workflow_dispatchによる secrets 窃取をすぐには実行できなかった - その代わり、デフォルトの CodeQL action はリポジトリの main branch で実行される
- GitHub リポジトリの main branch はリポジトリ全体で使われる cache entry を書き込めるため、GitHub Actions cache poisoning の機会が生じる
- GitHub Actions Cache Poisoning は Adnan Khan の記事 で扱われた手法であり、Cacheract は cache poisoning を通じて build pipeline に永続性を残す malware である
- 攻撃者が CodeQL workflow で Cacheract を展開すると、次の流れが可能だった
- cache entry を予測する
- その entry を悪意ある action で上書きする
actions/cacheを使う workflow でコード実行を得る- その workflow の GitHub Actions secrets と高権限の GITHUB_TOKEN を窃取する
- 悪意ある CodeQL action が発見されて脆弱性が対処されたとしても、Cacheract は cache poisoning を継続できた可能性がある
- CodeQL と
actions/cacheを併用する主要リポジトリの例として、Homebrew、Angular、Grafana が確認された
CVE-2025-24362 と修正
- この公開により CVE-2025-24362 が割り当てられた
- 公開された GITHUB_TOKEN は、失敗した code scanning workflow の後に CodeQL Action がアップロードした debug artifact 内に含まれていた
- CodeQL Actions リポジトリは意図的に失敗を発生させていたが、他の CodeQL Actions 利用者でも同様の失敗が起きていれば、workflow 環境変数を通じて自分たちの secrets を露出していた可能性があった
- この問題は CodeQL Action 3.28.3 で修正された
- 最大の潜在的影響は CVE 自体よりも、CodeQL Actions リポジトリに対して脆弱性を悪用し、CodeQL 利用者にサプライチェーン攻撃を仕掛ける経路にあった
GitHub の対応と推奨される緩和策
- GitHub の対応スケジュールは次のとおりだった
- 2025 年 1 月 22 日 15:13 UTC: GitHub に報告を提出
- 2025 年 1 月 22 日 17:48 UTC: GitHub が受領を確認
- 2025 年 1 月 22 日 18:28 UTC: GitHub が脆弱性を確認し、「PR Check – Debug artifacts after failure」workflow を一時的に無効化し、debug artifact アップロードを無効化する PR を提出
- 2025 年 1 月 24 日: GitHub が CVE-2025-24362 を割り当て、security advisory を公開
- GitHub Actions workflow artifact における secret 露出リスクを減らすための対策は次のとおり
- workflow artifact には特定のファイルやディレクトリだけをアップロードする
- 環境変数、
.git/config、runner の<path_to_runner_dir>/_work/_temp/ディレクトリ内ファイルを含む artifact のアップロードを避ける - GITHUB_TOKEN 権限を read-only に制限する
- artifact アップロード前に secret scan を実行する
1件のコメント
Hacker Newsのコメント
GitHubがimmutable actionsをもっと早く推し進めていれば、こうした影響もはるかに小さかったはず
この機能は現在のGitHub Actionsの攻撃対象領域の70%以上を防いでくれるはずだと、何度も繰り返し言い続けることになる。毎週のように事故が起きているのを見ると、そろそろリリースしてもよい頃だと思う
[1] https://github.com/features/preview/immutable-actions
この一時トークンが、新しいデプロイを作成し、アーティファクトの証明を生成する権限まで持っていた理由については説明がない
修正としてデバッグログをオフにしたと言っていたが、コード分析エンジンに合わせて一時トークンの権限をより適切に変更したのかには答えていなかった
以前はデフォルト値が許可的設定だけだったため、古い組織やリポジトリでは残念ながらこの設定が使われていることが多い
新しいリポジトリを作ると上位組織のデフォルト値を継承するため、誰かが変更しなければ、この安全でないデフォルトが残り続ける。ユーザー全体の設定はないので、個人所有の新規リポジトリは制限的なデフォルト値を使う。新しく作成した組織も、より良いデフォルト値を使うと理解している
[0]: https://docs.github.com/en/actions/security-for-github-actio...
CIとCDは完全に分離された環境であるべきだという確信がますます強まっている。CIが侵害されたからといって、CD関連のトークンが流出してはいけない
たとえば
"sub":"repo:octo-org/octo-repo:environment:prod"[1] のような条件を必ず満たすようにし、システムをさらに堅牢にしたければ他の[楽しいclaims][]も追加すればよい1: https://docs.github.com/en/actions/security-for-github-actio...
fun claims: https://github.com/github/actions-oidc-debugger#readme
対応時間は冗談ではなかった。GitHubの対応はかなり印象的だ
Praterという姓を持つ立場として、この名前がPraetorianに由来するので、praetorian.comを持っていたら本当に良かったと思う
公開GitHub Actionsを使うのは自ら問題を招く行為であり、ワークフロー手順を分析していないならなおさらだ
むしろwoodpeckerやその他の優れたCIビルダー(circle、travis、gitlabなど)を自前でホスティングする方がよい
OpenZFSのPRにCodeQLを適用しておいた。これはOpenZFSにとっては問題ではない。私たちのコードは秘密ではないからだ :)
それでも少なくとも迅速に解決されたのは幸いだ
これは修正されたのか?
このサイトは性能が悪すぎて、スクロールもほとんどできない