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이 노린 바로 그 지점입니다. 강화된 게이트는 새로 준비하고 따로 관리하는 두 빌더에서 같은 승인 커밋을 빌드하게 하고, 두 SHA-256 다이제스트를 상수 시간으로 비교해 조금이라도 다르면 서명을 거부합니다. 그러면 한 빌더에 임플란트가 있어도 서명된 릴리스 대신 불일치가 나옵니다. 공격자가 두 빌더를 모두 장악해야만 뚫리므로, 두 빌더는 자격 증명이나 이미지를 공유해서는 안 됩니다.

취약한 구현
using System;
using System.Diagnostics;

public static class ReleasePipeline
{
    // 빌드는 한 빌드 서버의 공유 쓰기 가능 작업 복사본을 사용하고
    // 결과물을 모두 서명합니다. 빌드 중 트리에 가해진 변경도 함께 서명됩니다.
    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
{
    // 격리된 빌더 두 개가 새 읽기 전용 체크아웃에서 동일한 고정 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
    }
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처