FlawOpen

flawopen.com/Incidents/solarwinds-sunburst-build-compromise-december-2020

● SolarWinds · CWE-506 / CWE-829 · September 2019 – December 2020 · 緊急
セキュリティ研究 · FlawOpen

SUNBURST:ビルドパイプラインそのものが脆弱性だったとき

SUNSPOTはビルド中にOrionのソースを改変し、署名済み更新にSUNBURSTを混入させました。有効な署名が証明するのは出所であり、コードの完全性ではありません。

💡 わかりやすい解説 (ELI5)

何千もの建物に、封印と押印をした保守キットを送る信頼された錠前業者を想像してください。誰かがキットを組み立てる工場に忍び込み、封印と押印の前に各キットへ小さな部品を一つ追加します。どの建物にも本当にその業者からのキットが届き、そのまま取り付けられます。封印は一度も破られていません。改ざんは封印の前に行われたからです。

主要な概念と専門用語

SUNSPOT
CrowdStrike が解析したビルドサーバー上のインプラント。MSBuild.exe が Orion をビルドするのを監視し、ビルドの間だけソースファイルをバックドア入りの版に差し替えました。
SUNBURST
SolarWinds.Orion.Core.BusinessLayer.dll に組み込まれたバックドア。最長 2 週間潜伏したあと、HTTP で攻撃者のインフラに接続しました。
コード署名
バイナリが鍵の保持者から来たことを示す暗号署名。それを生んだビルドが改ざんされたかどうかについては何も示しません。
再現可能なビルド
同じソースからビット単位で同一の出力を生むビルド。独立したビルド環境同士で結果を確かめ合えます。
CWE-506
組み込まれた悪意のあるコード。今回はソースリポジトリではなく、ベンダー自身のビルドの途中で挿入されました。

フレームワーク対応

インシデントのタイムライン

2019-09-04

攻撃者が SolarWinds の環境に侵入し、9 月 12 日に手口を試すためテストコードを Orion のビルドに注入する。

2020-02-20

SUNSPOT を通じて SUNBURST が Orion のビルドに組み込まれる。

2020-03-26

SolarWinds が、知らないうちに SUNBURST を含む Orion のホットフィックスの配布を始める。影響を受けたバージョンには 2019.4 HF5 と 2020.2 が含まれる。

2020-06-04

攻撃者がビルド環境から SUNSPOT を削除する。

2020-12-13

FireEye が SUNBURST の解析を公表し、CISA が連邦機関に Orion の切断を命じる Emergency Directive 21-01 を出す。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

ビルドサーバーが侵害される

攻撃者は SolarWinds のビルド環境へのアクセスを得て、Orion をコンパイルするサーバーに SUNSPOT を仕込みました。

ステップ 2

ビルドの途中でソースが差し替えられる

SUNSPOT は MSBuild.exe が Orion のソリューションをビルドしているのを見つけると、InventoryManager.cs をバックドア入りの版に置き換え、コンパイル後に元に戻しました。

ステップ 3

本物の鍵が結果に署名する

コンパイルされた SolarWinds.Orion.Core.BusinessLayer.dll は通常の署名工程を通ったため、バックドア入りの DLL は有効な SolarWinds の署名を持っていました。

ステップ 4

顧客が信頼されたアップデートを導入する

影響を受けたアップデートを導入した顧客は 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
    }
}

エンジニアリング&システム堅牢化チェックリスト

参考資料