はじめに
ソフトウェア開発を効率化するうえで欠かせない CI/CD パイプラインですが、実は コマンドインジェクション や カスタムアクションを介したサプライチェイン攻撃 など、見過ごされがちなセキュリティリスクをはらんでいることをご存じでしょうか。特に GitHub Actions のように世界中で広く利用されているプラットフォームは攻撃者の標的になりやすく、万が一侵害されれば GitHub Token を通じてリポジトリ全体を乗っ取られる危険性もあります。本記事では、CI/CD に潜む脆弱性と対策のポイントをわかりやすく解説します。
本記事の内容は、DEF CON 32 で行われたセッション「Your CI/CD Pipeline Is Vulnerable, But It’s Not Your Fault」(以下「DEF CON 32 セッション」)の発表内容を参考に構成しています。
セッションの当該箇所へのリンクも記載しているので、詳細を確認しながら読み進めていただければと思います。
CI/CD パイプラインの概要
CI/CD は、Continuous Integration(継続的インテグレーション)と Continuous Delivery/Deployment(継続的デリバリー/デプロイ)の略で、以下のプロセスを自動化する仕組みです
- ソースコードの変更を検知(プルリクエストやコミットなど)
- ビルドやテスト、セキュリティ検証などを自動実行
- 問題なければ本番環境へのデプロイ or デリバリ
手動作業を減らし、開発を素早く進められる反面、パイプライン自体が攻撃者の標的になり得ます。なぜなら、パイプラインにはクラウド環境の認証情報や秘密鍵など重要情報が集約されているからです。
GitHub Actions で狙われる理由
CI/CD ツールとして、GitHub Actions は特に注目されています。DEF CON 32 セッションでは、以下のような理由が挙げられていました(3:00〜3:52 付近)
- GitHub 全体で 4,000,000 以上のパブリックリポジトリが Actions を利用
- Pull Request や Issue など「イベント駆動」でフレキシブルに動作
- Secrets(認証トークンや鍵)の一元管理が可能
このように利便性が高いため、多くのリポジトリが導入し、結果として攻撃対象が増大します。
よくある脆弱性:コマンドインジェクション
コマンドインジェクションとは
Web アプリケーション開発などで良く知られる脆弱性の1つで、ユーザ入力を適切にサニタイズせず OS コマンドに渡してしまう状況を指します。
例えば以下のようなコードを想像してください。
(DEF CON 32 セッション 6:54〜7:52 付近)
1 | // ユーザ入力をそのまま OS コマンドとして実行 |
ここに "; rm -rf /
のような文字列を渡されると、想定外のOSコマンドを実行してしまいます。CI/CD でも同様のことが起こりうるのです。
GitHub Actions での例
GitHub Actions では、ワークフロー内に run: echo ${{ github.event.issue.title }}
のような記述があると、Issue タイトルに悪意のある文字列を仕込むことで任意のコマンドが実行される可能性があります。
1 | name: "Sample Workflow" |
上記の右下を見るとGitHub Actionsの実行ログにIssueのタイトルが出力されていることがわかります。つまり、GitHub Actionsの処理を実行しているOS上でechoコマンドが実行されているということです。
それでは、Issueのタイトルに下記画像の一番下の文字を登録されたら何が起きるでしょうか。
このスクリプトの内容を解読すると、、、
- gdbをインストール:プロセスのメモリをダンプするツールを準備。
- プロセスのメモリダンプ:Runner.Listenerという名前のプロセスのメモリ内容をk.dumpに保存。
- 機密情報を抽出:メモリから”value”(キーなど)と”issecret”:trueの情報を検索し、temp.txtに保存。
- 外部サーバーに送信:抽出した情報をHTTPリクエストで外部サーバーに送信。
この内容を実際にGitHubのIssueタイトルに登録してみましょう。
下記画像は、悪意のある外部サーバのログです。
外部サーバにGitHubプロジェクト上でsecretとして設定されている機密情報が送信されていることがわかります。
これらのsecretsの値に、たとえばAWS本番環境のアクセスキー等を登録していたら、AWSの本番環境に第3者がアクセスできるようになってしまうでしょう。
ユーザ入力を直接コマンド引数に渡すのではなく、環境変数に保存してから使うなどの対策が必要です。
GitHub トークンの危険性
GitHub Actions では、ワークフローごとに GITHUB_TOKEN
という特別なトークンが自動生成されます。このトークンは以下の操作が可能です(5:00〜6:00 付近)。
- リポジトリへのプッシュ(コード改ざん)
- リリースやタグの作成
- Issue や Pull Request の操作
もし攻撃者がこのトークンを奪取すると、リポジトリのオーナーやメンテナと同等の権限で操作されてしまいます。リポジトリを支配されるだけでなく、その成果物を利用するエンドユーザにも被害が拡大し得ます。
カスタムアクションがもたらすサプライチェインリスク
GitHub Actions には、コミュニティやサードパーティが作成した「 カスタムアクション (Composite Action) 」を容易に導入できます。
しかし、依存関係が階層的に広がるため、どこか1つにコマンドインジェクションの脆弱性があれば、利用者側のパイプライン全体が危険にさらされます(9:00〜12:00 付近)。自分のコードは安全でも、外部から持ち込んだアクションが脆弱だと意味がありません。
Bazel で見つかった脆弱性:具体例
Google のフラッグシップ OSS である Bazel(ビルド&テストツール)でも、カスタムアクションによりコマンドインジェクション脆弱性が見つかりました。
Bazel リポジトリ本体のワークフローに明示的な問題は見当たらずとも、別リポジトリで管理されているアクションに対するユーザ入力がサニタイズされていなかったケースです(約 14:00〜16:00 付近)。
- 攻撃者が Issue に悪意ある文字列を投稿
- ワークフローがトリガーされ、カスタムアクションのバグ経由でコマンドが実行
GITHUB_TOKEN
などのシークレットを奪取し、リリースやコード改ざんが可能に
大規模 OSS でこうした脆弱性があると、そのプロジェクトを使っている全ユーザや企業に影響が及ぶ恐れがあります。
GitHub Actions セキュリティ強化:ベストプラクティス
GitHub Actions を安全に使うためには、Secrets の管理からスクリプトインジェクション対策、さらに自己ホスト型ランナーやクロスリポジトリアクセスの管理まで、幅広い観点からのセキュリティ強化が必要です。以下では、公式ドキュメント(GitHub Docs: Security hardening for GitHub Actions) の主なポイントを抜粋してまとめています。
1. Secrets の安全な取り扱い
- Secrets 機能を利用する
機密情報はリポジトリの Secrets として暗号化して保存し、ワークフロー実行時にのみ使用する。- 例:DB パスワードや API キーを平文で workflow ファイルに書かない。
- 構造化データを避ける
JSON や YAML などにまとめて1つのシークレットにするのではなく、値ごとに個別のシークレットを作成。 - Secrets を使用した処理結果もシークレットとして登録
元のシークレットを加工して生成された値(JWT 等)は、ログに出た際に自動マスクされない場合がある。生成後もシークレットとして管理すると安全性が高まる。 - 最小権限の原則を遵守
GITHUB_TOKEN
は読取り専用 (read) をデフォルトにし、必要なステップだけwrite
にする。- シークレットを増やしすぎない、不要になったら削除する。
2. CODEOWNERS の活用
Using CODEOWNERS to monitor changes
- ワークフロー ファイルへの変更を必ずレビュー
.github/workflows
ディレクトリを CODEOWNERS に指定しておき、変更時に信頼できる人の承認を必須にする。 - 誤った設定や悪意ある修正を防ぐ
ワークフローの改ざんを防ぎ、セキュリティポリシーを維持するのに有効。
3. スクリプトインジェクションのリスクを理解する
Understanding the risk of script injections
- ワークフロー中でのユーザー入力は危険
たとえばgithub.event.issue.title
やgithub.event.pull_request.body
など、攻撃者が自由に書き換えられる入力を直接コマンドに渡すと、任意のコマンド実行につながる恐れがある。 - ブランチ名やメールアドレスも要注意
一見無害に見えるが、zzz";echo "hello";#
のように悪意ある文字列を含められる可能性がある。
4. スクリプトインジェクションを軽減するための対策
Good practices for mitigating script injection attacks
- アクションを作成して引数として渡す
- JavaScript アクションなどで、パラメータとして値を受け取り処理する。
- 直接シェルに文字列を埋め込まずに済むため、インジェクションリスクが下がる。
- 環境変数(env)を使う
env: TITLE: ${{ github.event.pull_request.title }}
のように、シェルコマンドを生成する前に変数へ代入する。"$TITLE"
のように二重引用符で変数を扱い、単純にコマンドがつながるのを防ぐ。
5. サードパーティアクションの安全な利用
- 信頼できるリポジトリかどうかを確認
- コードを監査し、秘密情報を勝手に送信したりしていないかチェック。
- SHA 固定でバージョンをピン留め
uses: actions/checkout@<commit-SHA>
のように、コミットハッシュで指定すると改ざんリスクを下げられる。
- タグ指定の場合は作者を信頼できるか確認
- タグは後から書き換え可能なので、作者の信頼性が高いかどうかを見極める。
6. Dependabot で依存するアクションを最新化
Using Dependabot version updates to keep actions up to date
- Dependabot で継続的にアクションのバージョンをチェック
- こまめなアップデートにより、脆弱性やバグの修正を取り込む。
- 手動管理の手間を軽減
- Dependabot が自動で PR を作成してくれるので、レビューしてマージするだけ。
7. Pull Request の作成や承認を制限
Preventing GitHub Actions from creating or approving pull requests
- ワークフローが自動で PR を作成・承認できないようにする
- 不正なコードが知らぬ間にマージされるリスクを低減。
- 組織やリポジトリの設定で Actions の動作を制限し、適切なプロセスを踏む。
8. コードスキャンを活用
Using code scanning to secure workflows
- CodeQL などで脆弱性を事前に検知
- GitHub Actions 用のコードスキャン機能を使うと、ワークフロー自体の脆弱性パターンも検出できる。
- OpenSSF Scorecards も活用
- Using OpenSSF Scorecards to secure workflow dependencies
- サプライチェインのリスクを自動でチェックし、開発者に警告を出してくれる。
9. ランナーのハードニング
Hardening for GitHub-hosted runners
Hardening for self-hosted runners
- GitHub ホストランナー
- 1 回のジョブ実行ごとにクリーン環境が用意され、永続化されないため安全性が高い。
- ただし完全に無害ではないので、Secrets やトークンなどの漏えいは注意。
- 自己ホストランナー
- 攻撃者が任意のコード実行に成功すると、ランナーを恒久的に侵害されるリスクがある。
- 公開リポジトリでの自己ホストは避ける、フォークからの PR には使わない、グループ分けで影響範囲を狭めるなどが推奨。
10. クロスリポジトリ アクセスに注意
Considering cross-repository access
GITHUB_TOKEN
はあくまで “1 リポジトリごと”- 複数のリポジトリに跨ったアクセスを想定すると、誤って大きな権限を与えてしまう可能性がある。
- 他リポジトリへのアクセスが必要なら
- Deploy key や GitHub App など、範囲を限定した認証手段を検討。
- 個人用トークン (personal access token) は、基本的に広範囲な権限を持つため注意が必要。
まとめ
- CI/CD パイプラインは非常に便利ですが、コマンドインジェクションやカスタムアクションの依存関係などのリスクが潜んでいます。
- 自社だけでなく、OSS の多層的な依存関係によるサプライチェイン全体を含めてセキュリティを考える必要があります。
- GitHub Actions には強力な
GITHUB_TOKEN
が自動付与されるため、ここを狙われると大きな被害に繋がる可能性があります。 - ユーザ入力を直接コマンドに渡さない、トークン権限を最小化するなどの基本的な対策が極めて重要です。
- 最小権限の原則を徹底し、Secrets やトークンを安全に管理する。
- スクリプトインジェクション対策としては、環境変数経由やアクション化などで直接的なコマンド連結を避ける。
- サードパーティアクションや自己ホストランナーの導入は慎重に行い、バージョンピン留めやアクセス制限でリスクを最小化。
- Dependabot や CodeQL、OpenSSF Scorecards などを活用し、継続的なセキュリティ強化を図る。
今後も本ブログでは、ソフトウェア開発やインフラ運用のセキュリティに役立つ情報を発信していきます。ぜひブックマークしていただけると幸いです!