2021年1月、攻撃者が人気のbashスクリプトに1行のコードを追加しました。数万の組織がそのスクリプトをダウンロードし、通常の継続的インテグレーションワークフローの一部として実行しました。各実行時、各コミット時、各パイプラインで、スクリプトはCIランナー内のすべての環境変数を忠実に未知のIPアドレスに送信しました。これは、誰かが気づくまで61日間続きました。
その後の数年は、これが異常事態だったという希望には優しくありませんでした。2024年のXZ Utilsバックドアは、MicrosoftエンジニアがSSHログインが半秒遅いことに偶然気づいたことで発覚しましたが、これは患者強いメンテナが約2年間信頼を築いた上で、ほぼすべてのLinuxディストリビューションに含まれる圧縮ライブラリにリモートコードパスを仕込んだものでした。
数ヶ月後のpolyfill.io乗っ取りは、数十万のサイトに埋め込まれたスクリプトを、ドメインの所有者が変わってから数日でマルウェア配信ネットワークに変えました。npmとPyPIレジストリは、誰も数えなくなった侵害されたパッケージの着実な鼓動に落ち着いています。Codecovはもはや警告の物語ではありません。それはテンプレートです。
「Codecovはもはや警告の物語ではありません。それはテンプレートです。」
この物語で興味深いのは、Codecovという会社が2021年に数ヶ月の苦難を経験したことではありません。攻撃を可能にしたセキュリティモデルであり、これは1つのベンダーに特有の異常ではありません。ほぼすべての現代的なソフトウェアが構築される方法の構造的特徴です。
テーゼはシンプルです:パイプラインが境界です。堀は本番環境の前にはありません。堀は本番環境をデプロイするものの周りにあります。
境界の問題
20年間、セキュリティアーキテクチャは本番環境を城とし、その外側を荒れ地として扱ってきました。ファイアウォール、WAF、ネットワークセグメンテーション、IAMポリシーは主に外向きです。それらを信頼できるのは、投げられる程度の距離でしかないのです。
問題は、現代の本番環境が城ではないことです。それは構築物です。コードコミットが成果物になり、成果物がデプロイメントになり、デプロイメントが顧客データベースへのアクセス権を持つ実行中のインフラストラクチャになります。その連鎖の至る所で、自動化システムは、よく見なければ実質的に「好きなことを何でもできる」という権限を持つ認証情報を保持しています。
CIランナーはAWSキーを保持します。ビルドエージェントはコンテナレジストリ認証情報を持っています。デプロイメントステップはKubernetesと通信できます。インターンのGitHub Actionは、原則としてmainにプッシュできます。これらのいずれかが侵害されれば、「本番」という区別は意味論的になります。
「新しいチーム、ツール、統合のたびに、秘密を読めるもののセットが広がり、パイプラインがそれが出荷するインフラストラクチャがすでに持っているガバナンスを得ることはほとんどありません。」
Codecovインシデントは典型的な例ですが、例外ではありません。パイプライン権限を持つツールが侵害されたときに起こることです。十分なツールと十分な時間があれば、侵害は統計的確実性になります。規模は誰も予算化しない変数です。新しいチーム、ツール、統合のたびに、秘密を読めるもののセットが広がり、パイプラインがそれが出荷するインフラストラクチャがすでに持っているガバナンスを得ることはほとんどありません。
何が起こったか
CodecovはBash Uploaderと呼ばれる小さなbashスクリプトを作成しました。これはCI実行からコードカバレッジレポートを収集し、処理のためにCodecovに返送するものでした。これを使用するには、CI設定に1行を追加します:curlでスクリプトを取得し、bashに直接パイプするものです。
スクリプトはGoogle Cloud Storageバケットでホストされていました。Codecovはセルフホスト製品のDockerイメージも構築・公開しており、それらのパブリックイメージの1つの中間レイヤーのどこかで、Google Cloud StorageサービスアカウントのHMACキーをベイクインしていました。これは、非常に多くのことを抱えている人に起こる種類のミスです。
2021年1月31日、攻撃者はDockerレイヤーからそのキーを抽出し、それを使ってBash Uploaderをバケット内で直接変更し、ダウンロードしたすべての顧客に修正版スクリプトを提供し始めました。修正は1行の追加で、約1,800行のスクリプトの525行目あたりにあり、便利なことに、コードカバレッジに関する退屈なことを行うとされるコードをほとんどの人が読み止める領域です。その行はenvコマンドを実行し、現在のシェルのすべての環境変数を出力し、その出力を攻撃者が管理するサーバーにPOSTしました。
CIランナーは、組織が所有するすべての機密情報を日常的に含んでいます:AWSアクセスキー、GitHubトークン、デプロイキー、データベース認証情報、Slack Webhook、統合するすべてのSaaSのAPIキーです。修正版スクリプトは、2ヶ月間、数万の顧客にわたるすべての実行で、それらすべてを忠実に漏洩させました。
侵害は2021年4月1日に発見されました。それを発見したのはCodecovでも侵入検知システムでもありませんでした。ダウンロードしたスクリプトをCodecovが公開したSHA256と照合し、一致しないことに気づき、それが意図的かどうかを尋ねるメールをCodecovに送った1人の顧客でした。
確認された下流被害者にはHashiCorp、Twilio、Rapid7、Confluentが含まれます。実際の被害範囲は不明です。漏洩した認証情報がプライベートリポジトリのクローンに使用されると、痕跡は他者の環境を通り抜け、その秘密を拾っていくからです。

これが露呈する構造的問題
Codecovを「Dockerイメージ衛生のミス」として片付けて次に進めたくなります。これは間違いです。Dockerレイヤーの問題は攻撃者がどのように侵入したかであり、攻撃が壊滅的だった理由ではありません。
攻撃が壊滅的だったのは、地球上のほぼすべてのCI/CDパイプラインに組み込まれた前提によるものです:パイプラインで実行することに合意したツールは、保持するすべての秘密を信頼できるという前提です。
その前提は狂っていません。ただ強制されていません。典型的なCIランナーには、「このステップは秘密Aを見られるが秘密Bは見られない」や「このステップはAWSに呼び出せるが他国のランダムなIPには呼び出せない」という意味のある概念がありません。ほとんどのパイプラインは、ステップがそこで働く人々によって設定されたという合理的な理論に基づいて、完全な認証情報バンドルをすべての実行ステップに渡します。
「攻撃が壊滅的だったのは、地球上のほぼすべてのCI/CDパイプラインに組み込まれた前提によるものです:パイプラインで実行することに合意したツールは、保持するすべての秘密を信頼できるという前提です。」
curl | bashでランナーに取り込むツールは、パイプラインの権限を継承します。タグで参照されるGitHub Actionも同様です。プルするコンテナイメージも同様です。数リリース前に依存関係に現れ、誰も特に注意を払っていなかったヘルパースクリプトも同様です。環境変数を読めるもののリストは、パイプラインで実行されるもののリストであり、ほとんどのセキュリティレビューが認めるよりもはるかに長く、四半期ごとに長くなっています。
これがまた、2021年以降サプライチェーン攻撃が減速していない理由です。Codecovの物語は、変奏と交代するキャストを伴って、数ヶ月ごとに異なるエコシステムで繰り返されています。攻撃クラスは構造的です。修正も構造的である必要があります。
修正の形
3つのガバナンス制御を組み合わせれば、Codecov侵害を脚注に変えられたでしょう。どれも特殊なものではありません。すべて、パイプラインを開発者の利便性ではなく本番インフラストラクチャとして扱うことを必要とします。
実際に実行されているものを検証する。最終的に攻撃を発見した顧客は、SHA256チェックサムを比較することでそれを行いました。Codecovはハッシュを公開していましたが、チェックしていた人はほとんどいませんでした。パイプラインは、既知の信頼できるソースに対して署名やチェックサムを検証せずに、任意の取得コード(スクリプト、コンテナ、Terraformプロバイダー、GitHub Action)を実行すべきではありません。タグではなくコミットSHAでActionを固定し、プロバイダー署名を検証し、IaCに静的スキャナーを実行してからクラウドアカウントに触れるデプロイメントを行うことは、もはや専門家の実践ではありません。Checkov、TFsec、Trivy、TFLintのようなスキャナーは、状態が触れられる前にパイプライン内のステップとして実行できます。これらは最低限の要件です。それらをオプションとして扱うコストは、2ヶ月の漏洩ウィンドウで支払われます。
パイプラインが通信できる場所を制限する。Bash Uploaderは、CIランナー内から任意のインターネットホストへのアウトバウンドHTTPSリクエストを作成することでデータを漏洩させました。ほとんどのCIランナーがこれを行えるのは、誰もできないと言わなかったからです。プライベートネットワーク内で実行され、既知の許可リストにエグレスが制限されたセルフホストまたはその他の制御されたランナーは、「ランダムなサーバーへのPOST env」を失敗したネットワークコールに変えます。VCS、クラウドAPI、宣言された外部依存関係のセットにしか到達できないランナーは、スクリプトの1つに静かに挿入されたものがあっても、一般的な認証情報漏洩ツールに再利用できません。
パイプラインに静的秘密を渡すのをやめる。これが最も結果の大きい変更であり、業界が最もゆっくりと内面化してきたものです。envが攻撃者にとって非常に生産的なコマンドだった理由は、ほとんどのCI環境が長寿命の静的認証情報で詰め込まれているからです:数年間有効なAWSアクセスキー、誰かがローテーションを思い出すまで有効なGitHub PATです。フェデレーテッドアイデンティティは問題の形を根本的に変えます。OIDCでは、パイプラインは認証情報を一切保存しません。どの環境にあり、どのワークフローが実行中で、どのコミットがデプロイされているかを記述する署名付きアサーションを提示し、クラウドプロバイダーはそのアサーションを、起ころうとしている操作に正確にスコープされた短寿命トークンと交換します。漏洩したアサーションは数分後には役に立ちません。漏洩したトークンは1時間以内に役に立たなくなり、アサーションで記述された条件外では使用できません。
OIDCを通じてAWS、Azure、GCP、Vaultに認証し、エグレス許可リストを持つネットワークセグメント内で実行し、依存する成果物とプロバイダーを検証するパイプラインは、Codecovシナリオがほとんど何も達成できない悪意のあるスクリプトを生み出すパイプラインです。env出力には長寿命の秘密は含まれません。漏洩先は到達不能です。修正版スクリプトは実行前に検証に失敗します。
これらの制御は初期侵害を防ぎません。初期侵害を面白くないものにします。これは次善の策であり、ほとんどの日において達成可能な目標です。

パイプラインを本番のように扱う
もう1つの変更があり、それは技術的というより文化的なものです。パイプラインは本番がすでに得ているのと同じガバナンスを必要とします。つまり、すべてのデプロイメントの監査ログ、機密スコープに触れる変更のための承認ポリシー(できればポリシーアズコード)、開発者が不注意にパイプラインに自分の役割が許可する以上の権限を与えられないようにするロールベースアクセスを意味します。また、コードが言っていることと実際に実行されていることのギャップが部族的知識ではなく、追跡可能でアラート可能な事実になるようにするドリフト検知も意味します。
「これらの制御は初期侵害を防ぎません。初期侵害を面白くないものにします。」
これは派手ではありません。また、CI/CDシステムが攻撃対象領域であることと、強制ポイントであることの違いでもあります。
これら4つの制御を並べてみると、1つのアイデアを表しています。パイプラインは、出荷するインフラストラクチャと同じ継続的ガバナンスに値します:1回限りのセキュリティレビューではなく、変更が実行される前、実行中、着地後の制御です。実行されるものを検証し、通信できる場所を制限し、静的認証情報を削除し、監査証跡と強制可能なポリシーですべてのデプロイメントを管理することは、プラットフォームにボルトで固定されたセキュリティ機能ではありません。それは、長年利便性として扱われてきたスタックの部分に適用されるガバナンスであり、チームが10のパイプラインを実行しようと1万を実行しようと、同じ方法で適用されます。
パイプラインは境界のレイヤーであり、現時点では最も弱いものです。その存在のほとんどで、それはセキュリティ境界ではなく生産性ツールとして扱われてきました。Codecovの攻撃者は2021年にそれを理解していました。公開Dockerイメージをスキャンしてレイヤーが多すぎるものを探し、今や5年前の誰よりも速く動いている次の攻撃者たちも、それを理解しています。私たちが同じように理解しなければ、増大する代償を払い続けることになります。
Group Created with Sketch.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.