flawopen.com/Incidents/solarwinds-sunburst-build-compromise-december-2020
SUNBURST:ビルドパイプラインそのものが脆弱性だったとき
SUNSPOTはビルド中にOrionのソースを改変し、署名済み更新にSUNBURSTを混入させました。有効な署名が証明するのは出所であり、コードの完全性ではありません。
何千もの建物に、封印と押印をした保守キットを送る信頼された錠前業者を想像してください。誰かがキットを組み立てる工場に忍び込み、封印と押印の前に各キットへ小さな部品を一つ追加します。どの建物にも本当にその業者からのキットが届き、そのまま取り付けられます。封印は一度も破られていません。改ざんは封印の前に行われたからです。
主要な概念と専門用語
SUNSPOT- CrowdStrike が解析したビルドサーバー上のインプラント。MSBuild.exe が Orion をビルドするのを監視し、ビルドの間だけソースファイルをバックドア入りの版に差し替えました。
SUNBURST- SolarWinds.Orion.Core.BusinessLayer.dll に組み込まれたバックドア。最長 2 週間潜伏したあと、HTTP で攻撃者のインフラに接続しました。
コード署名- バイナリが鍵の保持者から来たことを示す暗号署名。それを生んだビルドが改ざんされたかどうかについては何も示しません。
再現可能なビルド- 同じソースからビット単位で同一の出力を生むビルド。独立したビルド環境同士で結果を確かめ合えます。
CWE-506- 組み込まれた悪意のあるコード。今回はソースリポジトリではなく、ベンダー自身のビルドの途中で挿入されました。
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-506 CWE-829
- D3-OTF 送信トラフィックのフィルタリング — このガイドで説明する送信ネットワークの制限により、サービスまたはホストが接続できる宛先を絞れます。
インシデントのタイムライン
攻撃者が SolarWinds の環境に侵入し、9 月 12 日に手口を試すためテストコードを Orion のビルドに注入する。
SUNSPOT を通じて SUNBURST が Orion のビルドに組み込まれる。
SolarWinds が、知らないうちに SUNBURST を含む Orion のホットフィックスの配布を始める。影響を受けたバージョンには 2019.4 HF5 と 2020.2 が含まれる。
攻撃者がビルド環境から SUNSPOT を削除する。
FireEye が SUNBURST の解析を公表し、CISA が連邦機関に Orion の切断を命じる Emergency Directive 21-01 を出す。
ステップ・バイ・ステップの攻撃フロー
ビルドサーバーが侵害される
攻撃者は SolarWinds のビルド環境へのアクセスを得て、Orion をコンパイルするサーバーに SUNSPOT を仕込みました。
ビルドの途中でソースが差し替えられる
SUNSPOT は MSBuild.exe が Orion のソリューションをビルドしているのを見つけると、InventoryManager.cs をバックドア入りの版に置き換え、コンパイル後に元に戻しました。
本物の鍵が結果に署名する
コンパイルされた SolarWinds.Orion.Core.BusinessLayer.dll は通常の署名工程を通ったため、バックドア入りの DLL は有効な SolarWinds の署名を持っていました。
顧客が信頼されたアップデートを導入する
影響を受けたアップデートを導入した顧客は 18,000 未満でした。攻撃者はその後、はるかに少ない数の標的を選んで追加の侵入を行いました。
ソースコード比較:脆弱 vs 堅牢化
説明用の C# であり、SolarWinds のパイプラインではありません。脆弱なジョブは長期間動く単一のビルドサーバーを信頼し、その出力を何でも署名します。まさに SUNSPOT が突いた点です。強化版のゲートは、別々に管理され新たに用意した 2 台のビルド環境で同じ承認済みコミットをビルドさせ、SHA-256 ダイジェストを定数時間で比較し、少しでも違えば署名を拒否します。これにより、片方のビルド環境にインプラントがあってもリリースは署名されず、不一致として検出されます。攻撃者が両方を支配した場合にだけ破られるので、両者は認証情報もイメージも共有してはいけません。
using System;
using System.Diagnostics;
public static class ReleasePipeline
{
// ビルドは1台のサーバーにある共有の書き込み可能な作業コピーを使い、
// 生成物に署名します。ビルド中にツリーへ加えられた変更も署名対象になります。
public static void BuildAndSign(string project, string outputDll)
{
using var build = Process.Start("msbuild.exe", $"\"{project}\" /p:Configuration=Release");
build!.WaitForExit();
if (build.ExitCode != 0)
throw new InvalidOperationException("build failed");
Signer.Sign(outputDll); // HSM-backed signing service client
}
}using System;
using System.IO;
using System.Security.Cryptography;
public static class ReleaseGate
{
// 2つの分離されたビルダーが、新しい読み取り専用チェックアウトから同じ固定 commit をビルドし、
// /p:Deterministic=true を指定します。ゲートは出力が一致した場合だけ署名します。
public static void VerifyAndSign(string builderAOutput, string builderBOutput,
string builtCommit, string approvedCommit)
{
if (!string.Equals(builtCommit, approvedCommit, StringComparison.Ordinal))
throw new InvalidOperationException("build is not from the approved commit");
byte[] a = SHA256.HashData(File.ReadAllBytes(builderAOutput));
byte[] b = SHA256.HashData(File.ReadAllBytes(builderBOutput));
if (!CryptographicOperations.FixedTimeEquals(a, b))
throw new InvalidOperationException("independent builds differ; refusing to sign");
Signer.Sign(builderAOutput, Convert.ToHexString(a)); // digest is logged with the signature
}
}エンジニアリング&システム堅牢化チェックリスト
- ✓CI ランナーを本番環境と同じように守る。ソース、シークレット、署名鍵を持ち、顧客が実行するものを生み出すので、本番データベース並みの隔離、アクセス制御、監査ログが必要。
- ✓リリースは、別々に管理され新たに用意した 2 台のビルド環境で決定的コンパイルにより 2 回ビルドし、成果物のダイジェストが違えばリリースを失敗させる。
- ✓有効な署名を安全の証明とみなさない。署名が証明するのは出どころであり、その信頼性は鍵を保管していた環境の信頼性を超えない。
- ✓特権エージェントの外向き通信を制限する。SUNBURST は C2 サーバーに到達する必要があった。任意のインターネットホストと通信できる監視ツールの導入は、明示的にレビューした上での判断にする。
- ✓各ベンダーエージェントの権限は、導入のしやすさではなく、完全に侵害されたときに攻撃者が得るものを基準に決める。
- ✓攻撃者が改変できないテレメトリを残す。ホスト外の追記専用ログがあってこそ、数か月に及ぶ侵入を後から再構成できる。