FlawOpen

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

● SolarWinds · CWE-506 / CWE-829 · September 2019 – December 2020 · Crítica
Pesquisa · FlawOpen

SUNBURST: quando o pipeline de build era a vulnerabilidade

O SUNSPOT alterou o código-fonte do Orion no build e inseriu o SUNBURST em atualizações assinadas. A assinatura provava a origem, não a integridade.

💡 Explicação em Linguagem Simples (ELI5)

Pense num chaveiro de confiança que envia kits de manutenção lacrados e carimbados para milhares de prédios. Alguém invade a fábrica onde os kits são montados e acrescenta uma pecinha extra em cada um antes de o lacre e o carimbo serem aplicados. Todos os prédios recebem um kit que realmente vem do chaveiro e o instalam. O lacre nunca foi rompido, porque a adulteração aconteceu antes de lacrar.

Conceitos Centrais e Termos

SUNSPOT
O implante no servidor de build analisado pela CrowdStrike. Ele vigiava o MSBuild.exe compilando o Orion e substituía um arquivo-fonte pela versão com backdoor enquanto durava o build.
SUNBURST
O backdoor compilado em SolarWinds.Orion.Core.BusinessLayer.dll. Ficava inativo por até duas semanas e depois contatava a infraestrutura do atacante via HTTP.
Assinatura de código
Uma assinatura criptográfica que mostra que um binário veio de quem detém a chave. Ela não diz nada sobre se o build que o gerou foi adulterado.
Build reproduzível
Um build que gera saída idêntica bit a bit a partir do mesmo código-fonte, para que builders independentes possam conferir os resultados uns dos outros.
CWE-506
Código malicioso embutido. Aqui ele foi inserido durante o próprio build do fornecedor, não no repositório de código-fonte.

Mapeamentos de frameworks

Linha do Tempo do Incidente

2019-09-04

Os atacantes acessam o ambiente da SolarWinds; em 12 de setembro injetam código de teste num build do Orion para confirmar que o método funciona.

2020-02-20

O SUNBURST é compilado nos builds do Orion por meio do SUNSPOT.

2020-03-26

A SolarWinds começa a distribuir hotfixes do Orion que, sem que ela saiba, contêm o SUNBURST; entre as versões afetadas estão 2019.4 HF5 e 2020.2.

2020-06-04

Os atacantes removem o SUNSPOT do ambiente de build.

2020-12-13

A FireEye publica sua análise do SUNBURST e a CISA emite a Emergency Directive 21-01, mandando os órgãos federais desconectarem o Orion.

Fluxo de Ataque Passo a Paso

Passo 1

O servidor de build é comprometido

Os atacantes obtiveram acesso ao ambiente de build da SolarWinds e instalaram o SUNSPOT no servidor que compilava o Orion.

Passo 2

Um arquivo-fonte é trocado no meio do build

Quando o SUNSPOT via o MSBuild.exe compilando a solução do Orion, substituía InventoryManager.cs por uma versão com backdoor e restaurava o original após a compilação.

Passo 3

A chave legítima assina o resultado

O SolarWinds.Orion.Core.BusinessLayer.dll compilado passava pela etapa normal de assinatura, então a DLL com backdoor tinha uma assinatura válida da SolarWinds.

Passo 4

Os clientes instalam uma atualização confiável

Menos de 18 mil clientes instalaram atualizações afetadas. Depois, os atacantes escolheram um grupo bem menor de vítimas para invasões posteriores.

Código-Fonte: Vulnerável vs. Seguro

C# ilustrativo, não é o pipeline da SolarWinds. O job vulnerável confia num único servidor de build de longa duração e assina tudo o que ele produz, exatamente o que o SUNSPOT explorou. O gate reforçado exige dois builds do mesmo commit aprovado em builders provisionados do zero e administrados separadamente, compara seus digests SHA-256 em tempo constante e se recusa a assinar se houver qualquer diferença. Um implante num builder passa a gerar divergência em vez de uma release assinada; só falha se o atacante controlar os dois builders, por isso eles não podem compartilhar credenciais nem imagens.

IMPLEMENTAÇÃO VULNERÁVEL
using System;
using System.Diagnostics;

public static class ReleasePipeline
{
    // As compilações usam a cópia de trabalho compartilhada e gravável em um servidor e assinam
    // qualquer resultado. Também assinam alterações feitas na árvore durante a compilação.
    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
    }
}
PATCH SEGURO E ROBUSTO
using System;
using System.IO;
using System.Security.Cryptography;

public static class ReleaseGate
{
    // Dois compiladores isolados compilam o mesmo commit fixado em checkouts novos e somente leitura,
    // com /p:Deterministic=true. A etapa assina somente resultados idênticos.
    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
    }
}

Lista de Verificação de Segurança para Engenharia

Fontes