世界を動かす技術を、日本語で。

最も単純な方法でGitHub Actionsのポリシーを回避する

概要

  • GitHub Actions のポリシー機能は、アクションや再利用ワークフローの利用制限を目的とした仕組み
  • しかし、この仕組みには 簡単に回避できる脆弱性 が存在
  • GitHub側はこれをセキュリティ問題と認識していない が、筆者は問題視
  • 回避方法やその影響、対応策について詳細に解説
  • セキュリティ境界の誤認リスクがあるため 注意喚起

GitHub Actionsのポリシー機能とその回避

  • GitHub Actions はGitHubが提供する CI/CDサービス
  • ユーザーは ワークフロー内で実行するアクションや再利用ワークフロー の信頼性を十分に確認する必要
  • アクションは Actions Marketplace などから取得
  • 人気や活動状況、所有者の信頼性などで判断するが、 これらはあくまでヒューリスティック
  • 有名なアクションでも サプライチェーン攻撃 のリスク
  • 大規模なCI/CD構成では 管理が困難、未審査のアクション混入リスク

ポリシー機能の内容

  • Actions policies で利用できるアクション/ワークフローを 組織・リポジトリ単位 で制限可能
  • 設定例:OWNER/REPOSITORY@TAG-OR-SHA 形式で限定
    • 例: actions/javascript-action@v1.0.1
  • ワイルドカードカンマ区切り で複数指定も可能
  • 同一組織内のみ許可 などのプリセットも用意

ポリシーの回避方法

  • uses: で指定するアクションは、本来リモートリポジトリを参照

  • しかし、 ローカルパス(例: uses: ./) も指定可能

  • ワークフロー内で git clone 等でアクションリポジトリをローカルに落とし、 uses: ./tmp/checkout のように実行すれば ポリシーを回避 可能

  • 実際の例:

    - run: |
        mkdir -p ./tmp
        git clone https://github.com/actions/checkout.git ./tmp/checkout
    - uses: ./tmp/checkout
    
  • この方法で 制限を簡単に突破 できる現状

対応策とその課題

  • GitHub側の対策案
    • ローカルパス指定(uses: ./)も ポリシー対象 とし、許可されない場合は 拒否
    • これにより バイパス封じ が可能
    • ただし、 既存ユーザーの一部に影響 が出る可能性
  • もしくは、 現仕様の制限事項として公式ドキュメントで明示
    • 利用者が リスクを正しく認識 できるようにする

なぜこの問題が重要か

  • 形だけのポリシー は、セキュリティ境界として 誤認されやすい
  • 実際には 回避可能な仕組み であるにも関わらず、 安全だと誤信 されることでリスク増大
  • 多くの場合、 開発者が業務上仕方なくバイパス するケースが多い
  • GitHub側は修正または明確な注意喚起 を行うべき

まとめ

  • GitHub Actionsのポリシー機能 には 簡単な回避手段 が存在
  • セキュリティ境界の誤認 が組織・プロジェクトにリスクをもたらす
  • GitHub側の対応 (修正またはドキュメント明記)が望まれる
  • 利用者は 現状のリスクを十分認識 し、運用設計を行う必要

Hackerたちの意見

コードの変更でポリシーを「回避」できるっていうのは、そんなに深刻なことじゃない気がする。CI/CDのワークフローの変更を見直してないなら、もう希望はないよ。コードが流出したり、秘密が盗まれたりする可能性もあるからね。

単にレビューの問題じゃなくて、設定によっては、CIがプッシュやPR作成でトリガーされる場合、誰も変更を確認する前にこの回避策が実行されることもあるんだ。

この投稿のポイントは、実際のレビューは様々だってこと。大きな組織なら、コード自体の変更をレビューすべきだけど、多くの組織はCI/CDの変更で行われたすべてのアクション(そのバージョンも)を追跡してないと思う。それがポリシーの役割であり、回避策が危険な理由なんだ。直感的に言うと、ジュニアエンジニアを助けるためのブランチ保護や秘密のプッシュポリシーの価値が理解できるなら、CI/CDポリシーも同じように重要だよ。

「私たちの組織が公開したアクションと再利用可能なワークフローのみを許可します」と「私たちの組織が公開したアクションと再利用可能なワークフロー、または外部ソースから手動でダウンロードしたもののみを許可します」は、全然違うポリシーだよね。

これは「使えない安全なシステムを作ると、ユーザーはそれを使える不安全なものに変えてしまう」という典型的な例だね。誰かがこういうコントロールを積極的に無効化しているなら、そのコントロールはガードレールから線路の上のログに変わってしまったってことだ。AppLockerとかと同じような感じだね。ほとんどの人が使うべきだって言うけど、実際に使ってる人はほとんどいない。なぜなら、「受け入れ可能なソフトウェア」が何かを理解するだけでもものすごい労力がかかるから。

「使えない安全なシステム」の暗黙の解決策は、チェックアウトアクションを自分の組織にフォークして、そこで参照することだね。

ITセキュリティのバブルの外にいる人は、AppLockerを使うのが賢いアイデアだなんて思ってないよ。会社が従業員にどの特定のプログラムを使って仕事をするかを指示するのは、あまりにも過剰なマイクロマネジメントだと思う。

特定のリポジトリを手動でチェックアウトするだけじゃなくて、サブモジュールがあって再帰的にチェックアウトすると、予想もしなかった場所から他のセキュリティの悪夢を引き込むこともできるんだ。とはいえ、それを実行するのはかなり複雑な攻撃になるだろうけど、妥協されたワークフローの連鎖って感じだね、ハハ。

だから、できるだけ非公式なアクションは使わないようにして、アクションのバージョンは必ず設定するようにしてる。以前、契約者がランダムなアクションを使ってファイルをサーバーにSSHで送信して、バージョンとしてmasterを参照してたことがあったんだ。まず、SSHでファイルをアップロードしてコマンドを実行するのはそんなに難しくないし、アクションの所有者が簡単にプライベートキーや情報を別のサーバーに保存するコードを追加できるからね。「回避」についてはちょっと混乱してるけど、敵がワークフローファイルを編集するためにはリポジトリへのプッシュアクセスが必要じゃないの?だから、強化すべき部分は、間違った人がリポジトリにファイルをプッシュできないようにすることなんだよね。公開リポジトリでは、PRが作成されたときに実行されるワークフローのセクションでそれをやられたら問題になるかもしれない。プライベートリポジトリでは、アクセスを与える相手には気をつけるべきだね。

「だから、できるだけ非公式なアクションを使わないようにして、常にアクションのバージョンを設定するようにしてる。これはいいプラクティスだね。バージョン(タグ)を固定するだけでは不十分だと思う。tj-actions/changed-filesイベントで学んだように、コミットSHAを固定すべきだよ。」 [0] GitHubは公式ドキュメントでもこれを述べてるね。[1] > 「アクションを完全なコミットSHAに固定する」 > 「アクションをタグに固定するのは、クリエイターを信頼している場合のみ」 [0] https://www.stepsecurity.io/blog/harden-runner-detection-tj-... [1] https://docs.github.com/en/actions/security-for-github-actio...

「バイパス」についてちょっと混乱してるんだけど、敵がワークフローファイルを編集するにはリポジトリへのプッシュアクセスが必要じゃない?だから、強化が必要なのは、間違った人がリポジトリにファイルをプッシュできないようにすることだよね?私もそう理解してる。でも、会社全体のポリシー(アクションに関して)が、悪意のある開発者や不注意な開発者から会社を守るためのセキュリティ対策として誤解されたり使われたりするかもしれない。だから、その振る舞いを文書化したり強調したりすることで、DevOpsの人たちが誤った安心感を持たないようにするのが大事だと思う。それ以上はないかな。

ワークフローやスクリプトを設定しているときに、全く同じことを考えてた。いつ何が許可されるかの不当で長引く制限に悩まされてるからね。何かをやり方を調べると、10年以上前に開かれた問題がトップに出てくると、ほんとに沈む気持ちになるよね…。ローカルホストのGitLabで仕事してたから、GitHubを使って何か役立つことをしようとすると特に辛い。ドキュメントを何度か試してみたけど、キャッシュの設定がうまくいかなくて諦めちゃった。だって、金払ってるわけじゃないし。CodeQLの推奨設定が、軽い使用で2600分以上もアクションを消費してたのにはびっくりした。数週間の重い利用で得た合計のほぼ倍だよ。これ、誰が払ってるの?

「開発者のPAT以外で内部/プライベートリポジトリをクローンできないなんて、信じられない。ワークフローのアクセスを共有するためのUIがあるんだから、クローンにもそれを使わせてほしい…」

「たった1日のために1.8日も時間がかかったの?誰がそれを支払っているのかよりも、あなたのリポジトリで誰が 使っている のかが気になる。毎分ほぼ2人がコードベースをスキャンしているなんて、想像もつかない。」

正直、これって大したことじゃない気がする。うちの職場でのポリシーとその実施方法に対する主な問題は、ポリシーを設定している人たちがその影響を受けてないこと、そして影響を受ける人たちに相談しないことだね。うちのセキュリティチームはGitHubの管理チームに、サードパーティのアクションは使えないって言ってる。GitHubの管理チームは「はい、わかりました」って感じ。彼らはアクションを使わないから気にしないし、実際何も提供してない。セキュリティチームも何も提供しないから、彼らも気にしない。これらのチームの成果と言えば、GitHub Enterpriseを買って、過去7年でクラウドとオンプレミスを3回行き来したことくらい。開発者としては、使いたいアクションを読んで、良さそうならコードをクローンして自分たちの組織やリポジトリにアップロードするよ。もうすでに、何が起こるかわからない無数のnpmモジュールを同じコンテキストで実行してるし。誰かが文句を言ったら、他のコードや依存関係と同じ静的/動的分析ツールにかけられるだけだし。

コードを読んでフォークすることで(悪意のある更新を防ぐ)、ポリシーの意図を完全に満たすってことだね。私の会社にも似たようなアクションのホワイトリストがあって、評価されて却下されたサードパーティのアクションのリストがある。却下されたものの多くはリリースを作るためのヘルパーみたいで、ランナー上でgh CLIを使うことをほぼ一律に提案してる。

ここでのセキュリティ問題が見えないんだけど。任意のコード実行が任意のコード実行につながる?実行できるものに関してポリシーを強制するのは難しいから、唯一の対策は秘密情報へのアクセスを制限することだよね。何かの秘密にアクセスしたり盗んだりできるデモはあるの?

「これは『無防備な被害者に対して悪いことをするために使う』ってより、『実際にユーザーを制限するポリシーが必要なのに、みんながそのポリシーを回避できる』って感じだね。 1. 大企業のIT部門が、コンプライアンスのために外部アクションを無効にするためにチェックボックスをクリックする [0] 2. 大賢い開発者がExternalActionを使いたいから、投稿に記載されている方法を使う。頭がいいからね。 3. 大企業はもはやコンプライアンスを守っていないし、もっと重要なのは、中央IT部門がこれが起こっていることを、サポートしているすべてのチームのCIワークフローを常にチェックしない限り全く知らないってこと。 [1] だから、他の誰かが言ってた「アクションを自分の組織にフォークするべき」っていうのは、ローカルのuses:を無効にするオプションがチェックボックスに追加されれば解決策になる。中央IT部門は、誰が何を使っているかを把握できるからね。大賢い開発者がExternalActionをBigEnterpriseOrgのGH組織にフォークするように頼めば、中央IT部門の関与はコードベースのレビュー、フォーク、更新の維持だけになる。 注意:これはコンプライアンスに反するすべてのことに対する万能薬ではない(外部バイナリのダウンロードなど)。でも、簡単にギャップを埋めることができる。 ---- [0]: まあ、企業のIT部門が内部の開発者をイライラさせる理由はいろいろあるよね。[1]: これは、内部の開発者全員をイライラさせる確実な方法だね。」

「ポリシーを強制するのは不可能のようだ」 著者はまさにそれに共感してるね。「効果的でないポリシーのメカニズムは、ポリシーがないよりも悪い。なぜなら、コンプライアンスを通じて安全を感じさせる一方で、悪意のあるコンプライアンスを促進してしまうから。」 これには完全に同意するよ。本当に多いよね。「はい、私たちはすべての強力なパスワード要件に準拠しています。厳密に言えば、私たちが使っているすべてのサービスに対して、各管理者ユーザーに一つの強力なパスワードがありますが、それはチェックリストには載っていないよね?」

正直、リスクが理解できない。リポジトリにコードを書ける人は、すでにGitHubアクションで何でもできるからね。このセキュリティ対策は、開発者が悪意のあることをするのを防ぐために設計されたわけじゃない。別のアクションをリポジトリにクローンするか、自分でカスタムスクリプトを書くか、GitHubの対策がそれを防げるとは思えない。

このポリシーの緩和策は投稿に含まれてるよ。(ポイントは、直接的な悪意のある導入ではなく、エンジニアが導入するアクションや再利用可能なワークフローの供給チェーンリスクだよ。それ自体が柔軟でリスクにさらされるもの。そういうことをするポリシーは、実際にそれを実行するか、その制限を明示的に文書化すべきだ。)

「リスクは、私たちのサーバーが限られたホストリスト以外に外部ネットワーク接続を許可しない理由と同じだ。例えば、バックエンドサーバーはゲートウェイ、キュー/データベース、API用の承認されたドメインリストとだけ通信できる。他の何にも接続できない。このガードは、悪意のある行動を防ぐだけでなく、事故やセキュリティ侵害も防ぐ。もしコードが何らかの形で私たちのシステムに入っても、ほとんどの外向き接続を防いでいるから、情報を持ち出すのがずっと難しくなる。そう、コードレビューは行われるけど、何かが見逃されることもある。例えば、Googleがmkdirを実行するためにシェルを使っていたコアライブラリの一つを切り替えたことがある。mkdir -pを実行するように(タダ!すべての呼び出しがシェルのエスケープルールをよりよく理解するようになる)。それはコードレビューを通過した。人間は完璧ではないから、ネットワークに「この小さなリスト以外の外向き接続は許可しない」と言うのは、完璧に近い。」

これを試したことはないけど、主なリスクはユーザーがプルリクエストで動くアクションを持つ公開リポジトリにPRを作成することだと思う。

リスクはシンプルだよ。GitHub Enterpriseは管理者が許可または拒否するアクションのリストを設定できる。理想的には、これらのアクションはGitHub Marketplaceに公開されているはず。組織はこれらのサードパーティを信頼していないから、アクセスを無効にするんだ。でも、この解決策はオープンソースのアクションを直接ランナーにクローンすることでそのリストをバイパスしちゃう。その時点で、メンテナが複雑なアクションを自分で書いたのと変わらない、ただのコードを実行してるだけだよ。

こういうことを気にする会社は、CIの設定を実際のコードとは別のリポジトリに置いてるから、開発ブランチをそのまま本番にデプロイするために書き換えることはできないよ。

うん…君の言う通りだね。馬鹿なことに、GitHubは「アクションポリシー」を提供して、本当に何かをしているかのように振る舞ってる。

GitHubのCI/CDの提供が今は「オールイン」すぎる気がする。SCMツールが2010年頃のAWSのスーパーセットになったら、ちょっと引いて代替案を考える必要があるかも。理想的なアプローチは、リポジトリのオーナーが外部ツールを統合できるシンプルなREST APIやWebhookを公開することかもしれない。yamlファイルや変な共有ライブラリのアクションをいじるより、PythonやC#でCI/CDツールを書く方がずっといい。今でもそれに近いことはできるけど、ある程度はGHアクションを使わないといけない。PRはほとんどレイテンシに敏感じゃないから、60秒ごとにREST APIをポーリングするのは許容範囲だと思う。これは基本的にJenkinsでやってたことと同じで、変なAPIの代わりにリポジトリのヘッドをポーリングしてたんだ。

GitHubにはWebhookと広範なAPIがあるよ。君が説明していることは全然可能だし、私の知る限りGitHubアクションは必要ない。ほとんどの人は便利だから選んでるだけだよ。yamlや共有アクションの間でバランスを取ることができるし、自分のスクリプトを実行することもできる。

「より理想的なアプローチは、リポジトリの所有者がステータスチェックを強制するためにより適した外部ツールを統合できるように、シンプルなREST APIやWebhookを公開することかもしれない。」 それは…何年も前から存在してるよね? https://docs.github.com/en/rest?apiVersion=2022-11-28 それはGitHub Actionsの前に唯一利用可能だったものだし、マージキューの前に「ロケットサイエンスではない原則」を実装したい場合にも唯一の選択肢だった。無料には勝てないけど、特にOSSのメンテナンスに関してはね。GHAは、オーケストレーター(または完全にカスタムなソリューション)を維持する必要がある並列性を提供してくれるし、複数のジョブやワークフローを作成するだけで済む。トークンを使ってステータスを送信する必要もないし、すべてのログやフィードバックがGitのインターフェースで得られるから、再度持ち込む必要もない。リベースやスクワッシュしたときにPRをマージ済みとしてマークすることもできる(これは今、中学生のリクエストにある機能だね:https://github.com/isaacs/github/issues/2) > PRはほとんど遅延に敏感ではないから、REST APIを60秒ごとにポーリングするのは私には許容範囲だと思う。ポーリングするものは何もないよ:https://docs.github.com/en/webhooks/types-of-webhooks

そもそもGitHubの人気が理解できない… gitは相互運用可能なバージョン管理「プロトコル」としてあるのに、そこに独自のイシュー、PR、CI、プロジェクト管理機能を追加して、移行する際に持っていけないものを載せるの?その時点で、gitの上に構築されている意味って何なの?それに、gitの良いところはたくさんあるけど、最高のバージョン管理システムだとは全然思わない。ここで真剣に車輪の再発明をしたいな。

「私たちはアクションをサブモジュールとしてフォークして、そのディレクトリを指すようにした。そうすることで、チームとして承認した個々のコミットを追跡できた。今、面白い二項対立がある。一方ではPMがGitHub Actionsを活用して、事前に作られたブロックを使ってもっと早く構築するように求めているけど、他方ではセキュリティ部門はアクションをホワイトリストに載せることに興味も能力もない(記事によるとホワイトリストは100アクションに制限されているし)。とはいえ、GitHubアクションにsha256をタグ付けすることも完全ではない。コンテナアクションはタグを参照でき、そのタグの内容は変更される可能性があるからね。例えば、私は次のようなコードのアクションを公開する: runs: using: 'docker' image: 'docker://optionoft/actions-tool:v3.0.0' あなたがそのアクションを使って、このコミットのSHAに固定する。私がハッキングされて、ハッカーが新しいバージョンのoptionoft/actions-tool:v3.0.0を公開する。そうすると、Dependabotの更新PRすら来ないよ。」

将来的にDependabotが、使用中のタグが変わったときにFYIの問題を作成する機能が追加されるかもね?