Goブログ

Goがサプライチェーン攻撃をどのように緩和するか

Filippo Valsorda
2022年3月31日

現代のソフトウェアエンジニアリングは共同作業であり、オープンソースソフトウェアの再利用に基づいています。そのため、ソフトウェアプロジェクトが依存関係の侵害によって攻撃されるサプライチェーン攻撃の標的にさらされています。

どのようなプロセスや技術的な対策を講じても、すべての依存関係は必然的に信頼関係となります。しかし、Goのツールと設計は、さまざまな段階でリスクを軽減するのに役立ちます。

すべてのビルドは「ロック」されています

依存関係の新しいバージョンが公開されるなど、外部環境の変化がGoビルドに自動的に影響を与えることはありません。

他のほとんどのパッケージマネージャーファイルとは異なり、Goモジュールには、個別の制約リストと特定のバージョンを固定するロックファイルがありません。Goビルドに寄与するすべての依存関係のバージョンは、メインモジュールのgo.modファイルによって完全に決定されます。

Go 1.16以降、この決定性はデフォルトで適用され、ビルドコマンド(go buildgo testgo installgo runなど)は、go.modが不完全な場合に失敗しますgo.mod(したがってビルド)を変更する唯一のコマンドは、go getgo mod tidyです。これらのコマンドは自動的に、またはCIで実行されることは想定されていないため、依存関係ツリーの変更は意図的に行われ、コードレビューを受ける機会が必要です。

これはセキュリティにとって非常に重要です。CIシステムまたは新しいマシンでgo buildを実行すると、チェックインされたソースがビルドされる内容の最終的で完全なソースとなるためです。サードパーティがそれに影響を与える方法はありません。

さらに、go getで依存関係が追加されると、最小バージョン選択のおかげで、その推移的な依存関係は、依存関係のgo.modファイルで指定されたバージョンで追加されます。最新バージョンではありません。go install example.com/cmd/devtoolx@latestの呼び出しでも同じことが起こります。これは、一部のエコシステムでは同等のものがピン留めをバイパスします。Goでは、example.com/cmd/devtoolxの最新バージョンがフェッチされますが、すべての依存関係はそのgo.modファイルによって設定されます。

モジュールが侵害され、新しい悪意のあるバージョンが公開された場合、エコシステムがイベントを検出する機会と時間を与え、変更をレビューする機会が提供されるまで、依存関係を明示的に更新するまで、誰も影響を受けません。

バージョンの内容は変更されません

サードパーティがビルドに影響を与えることができないようにするために必要なもう1つの重要な特性は、モジュールバージョンの内容が不変であることです。依存関係を侵害した攻撃者が既存のバージョンを再アップロードできる場合、それに依存するすべてのプロジェクトを自動的に侵害する可能性があります。

それがgo.sumファイルの目的です。ビルドに寄与する各依存関係の暗号化ハッシュのリストが含まれています。繰り返しますが、不完全なgo.sumはエラーを引き起こし、go getgo mod tidyのみがそれを変更するため、変更は意図的な依存関係の変更に伴います。他のビルドは、完全なチェックサムセットを持っていることが保証されています。

これは、ほとんどのロックファイルの共通機能です。Goは、チェックサムデータベース(略してsumdb)を使用して、それを超えています。これは、go.sumエントリのグローバルな追加専用で暗号化的に検証可能なリストです。go getgo.sumファイルにエントリを追加する必要がある場合、sumdbの整合性の暗号化証明とともにsumdbからエントリをフェッチします。これにより、特定のモジュールのすべてのビルドが同じ依存関係の内容を使用するだけでなく、すべてのモジュールが同じ依存関係の内容を使用することが保証されます。

sumdbは、侵害された依存関係、あるいはGoogleが運営するGoインフラストラクチャでさえ、変更された(例えば、バックドアされた)ソースで特定の依存関係をターゲットにすることを不可能にします。たとえば、example.com/modulexのv1.9.2を使用しレビューした他のすべての人と同じコードを使用していることが保証されます.

最後に、sumdbのお気に入りの機能:モジュール作成者による鍵管理を必要とせず、Goモジュールの分散型の性質とシームレスに連携します。

VCSは真実の源です

ほとんどのプロジェクトは、バージョン管理システム(VCS)を通じて開発され、他のエコシステムではパッケージリポジトリにアップロードされます。つまり、侵害される可能性のあるアカウントは、VCSホストとパッケージリポジトリの2つです.後者は使用頻度が少なく、見落とされがちです。また、リポジトリにアップロードされたバージョンに悪意のあるコードを隠す方が簡単です。特に、ソースがアップロードの一部として定期的に変更される場合(たとえば、最小化するため)はそうです。

Goには、パッケージリポジトリアカウントのようなものはありません。パッケージのインポートパスには、go mod downloadVCSからモジュールをフェッチするために必要な情報が埋め込まれており、タグはバージョンを定義します。

Goモジュールミラーはありますが、それはプロキシにすぎません。モジュール作成者はアカウントを登録せず、プロキシにバージョンをアップロードしません。プロキシは、goツールが使用するロジックと同じロジック(実際、プロキシはgo mod downloadを実行します)を使用して、バージョンをフェッチしてキャッシュします。チェックサムデータベースは、特定のモジュールバージョンに対してソースツリーが1つだけ存在できることを保証するため、プロキシを使用するすべての人は、プロキシをバイパスしてVCSから直接フェッチするすべての人と同じ結果を確認します。(バージョンがVCSで利用できなくなった場合、または内容が変更された場合、直接フェッチするとエラーが発生しますが、プロキシからフェッチすると引き続き機能する可能性があり、可用性が向上し、エコシステムが「left-pad」の問題から保護されます。)

クライアントでVCSツールを実行すると、かなり大きな攻撃対象領域が公開されます。それがGoモジュールミラーのもう1つの利点です。プロキシ上のgoツールは堅牢なサンドボックス内で実行され、すべてのVCSツールをサポートするように構成されていますが、デフォルトでは2つの主要なVCSシステム(gitとMercurial)のみがサポートされます。プロキシを使用する人は誰でも、デフォルトでオフになっているVCSシステムを使用して公開されたコードをフェッチできますが、攻撃者はほとんどのインストールでそのコードにアクセスできません。

コードのビルドはコードを実行しません

コードのフェッチもビルドも、信頼できない悪意のあるコードであっても、そのコードを実行できないようにすることは、Goツールチェーンの明示的なセキュリティ設計目標です。これは、他のほとんどのエコシステムとは異なります。他の多くのエコシステムでは、パッケージのフェッチ時にコードを実行するためのファーストクラスのサポートがあります。これらの「ポストインストール」フックは、過去に、侵害された依存関係を侵害された開発者マシンに変え、モジュール作成者を介してワームするための最も便利な方法として使用されてきました。

公平を期すために言えば、コードをフェッチする場合は、開発者マシンのテストの一部として、または本番環境のバイナリの一部として、 shortly afterwards実行することが多いため、インストール後のフックがないと攻撃者の速度が低下するだけです。(ビルド内にセキュリティ境界はありません。ビルドに寄与するパッケージはすべてinit関数を定義できます。)ただし、バイナリを実行したり、モジュールの依存関係のサブセットのみを使用するパッケージをテストしたりする可能性があるため、意味のあるリスク軽減策となる可能性があります。たとえば、macOSでexample.com/cmd/devtoolxをビルドして実行する場合、Windowsのみの依存関係またはexample.com/cmd/othertoolの依存関係がマシンを侵害する方法はありません.

Goでは、特定のビルドにコードを提供しないモジュールは、セキュリティに影響を与えません。

「少しのコピーは、少しの依存関係よりも優れています」

Goエコシステムにおける最終的でおそらく最も重要なソフトウェアサプライチェーンリスクの軽減は、最も技術的でないものです。Goには、大規模な依存関係ツリーを拒否し、新しい依存関係を追加するよりも少しコピーすることを好む文化があります。それはGoのことわざの1つにまでさかのぼります:「少しのコピーは、少しの依存関係よりも優れています」。「ゼロ依存関係」というラベルは、高品質の再利用可能なGoモジュールによって誇らしげに着用されています。ライブラリが必要になった場合、他の作成者と所有者による数十の他のモジュールへの依存関係を引き起こさないことがわかる可能性があります。

これは、豊富な標準ライブラリと追加モジュール(golang.org/x/...のもの)によっても実現されています。これらは、HTTPスタック、TLSライブラリ、JSONエンコーディングなど、一般的に使用される高レベルのビルディングブロックを提供します。

これらをすべてまとめると、ほんの一握りの依存関係で豊かで複雑なアプリケーションを構築できることを意味します。ツールがどれほど優れていても、コードの再利用に伴うリスクを排除することはできないため、最も強力な軽減策は常に小さな依存関係ツリーになります。

次の記事:ワークスペースに慣れる
前の記事:ジェネリクスの紹介
ブログインデックス