Supply Chain Security

What Is a Build Pipeline Compromise?

HuntRule Team · · 9 min read

A sealed crate on a conveyor belt emerging from an enclosed steel tunnel, its wax seal intact and catching the light.
On this page

SolarWinds.Orion.Core.BusinessLayer.dll shipped with a valid SolarWinds Authenticode signature and a backdoor inside the same assembly. The git history was clean. The certificate was the real one. Every integrity check a customer could run came back green, and the DLL still called out to avsvmcloud.com.

That is a build pipeline compromise. The attacker touches neither the repository you can audit nor a key you can revoke. They stand between the source and the artifact, in the one window where nobody is looking: the source is already reviewed, the artifact is not yet signed. It breaks the assumption that a signed build is a build of the code you read.

Where the injection happens

There are five places to put code between a commit and a download.

  1. The source at checkout. The repository is untouched, but the working tree on the build machine is modified after clone and before compile. Nothing is pushed back, so git status on a developer laptop shows nothing.

  2. A dependency resolved at build time. A floating version range, a mutable tag or a compromised registry account pulls a different package than the one reviewed. ATT&CK splits this out as T1195.001.

  3. The build tool or the compiler. The toolchain emits code that is in no input file. Thompson's trusting trust with a package manager attached.

  4. A post-compile step. Packers, obfuscators and installer generators run after the compiler and before signing, and are usually shell scripts nobody reviews.

  5. Signing and publishing. A signing service signs whatever it is handed. Take the credential or the request path and the artifact gets a genuine signature.

MITRE covers the outcome as T1195.002, Supply Chain Compromise: Compromise Software Supply Chain. The ATT&CK entry for the 3CX campaign describes the same shape, where the second stage "modified the Windows and macOS build environments used to distribute the 3CX software to their customer base".

SUNBURST: injection at step one

FireEye (now Mandiant) published the SUNBURST analysis on 13 December 2020, tracking the actor as UNC2452 and later merging it into APT29 in April 2022. The trojanized component was SolarWinds.Orion.Core.BusinessLayer.dll, a digitally signed part of the Orion framework. Multiple signed trojanized updates were posted between March and May 2020.

The build-injection detail came a month later. CrowdStrike documented SUNSPOT, the implant that lived on the SolarWinds build server:

  • It monitored running processes for MsBuild.exe, identifying them by hashing the process name with ElfHash and comparing against 0x53D525. The loop ran every second.

  • On a hit it read the command line out of the target's virtual memory via NtQueryInformationProcess, then parsed the arguments for a hard-coded Orion Visual Studio solution directory path, stored AES128-CBC encrypted inside the implant.

  • If the path matched, it replaced a source file in the solution directory. The identified sample only replaced InventoryManager.cs.

  • The original was copied to InventoryManager.bk. The malicious source was written as InventoryManager.tmp and moved over the original with MoveFileEx.

  • Before replacing, it verified an MD5 of the backdoored source (5f40b59ee2a9ac94ddb6ab9e3bd776ca), so a failed decryption would not cause a build error and start an investigation.

Read that with a defender's eye. MSBuild compiled a consistent, parseable source tree. The compiler behaved correctly. The signing step signed what it was given. The pipeline did its job on inputs swapped one second before they were read.

The backdoor was patient. Microsoft's analysis notes that Initialize checks the host process is named solarwinds.businesslayerhost.exe and that the DLL's last write time is at least 12 to 14 days earlier, and refuses to run if the device domain contains "solarwinds".

What the defences actually do

Reproducible builds. The definition is narrow and useful: a build is reproducible if, given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts, verified by hash comparison. This is the control that catches step-one injection, because an independent rebuilder starting from the pristine repository produces a different binary and the mismatch is the alarm. The cost is real. Timestamps, build paths, locales and archive metadata all leak into outputs, and for .NET a deterministic compiler switch is not a reproducible pipeline. We found no public reporting that SolarWinds ran independent rebuilds in 2020.

Build provenance and SLSA. The build track has four levels. L0 has no requirements. L1 requires provenance describing what entity built the package, what process was used and what the top-level inputs were, and the spec is blunt that this "can be used to prevent mistakes but is trivial to bypass or forge". L2 requires provenance generated and signed by a hosted build platform, targeting tampering after the build. L3 requires a hardened platform with strong controls to prevent runs from influencing one another and to keep provenance signing material out of reach of user-defined build steps, targeting tampering during the build.

The boundary matters. The build track measures the platform, not whether the source tree on that platform matched the repository. Provenance generated during a SUNSPOT-style build would have recorded a real commit, a real builder and a real process, and all of it would have been true. SLSA v1.2 is the current version and adds a source track, which is the piece that speaks to source integrity. Hermeticity and reproducibility are required at no current build level.

Hermetic and isolated builds. Ephemeral builders, no network during compile, all inputs declared and content-addressed. This closes step two hard and shrinks the window for steps three and four, since no long-lived host remains for an implant to sit on. It does not verify that the declared source input is the reviewed source input.

Artifact signing. Signing proves origin. It does not prove innocence. SolarWinds signed SUNBURST, the signature was valid, and validating it told customers only that SolarWinds built the file.

A brass stamp pressing into a pool of hot wax on a plain envelope, the wax still glowing at the rim.

Detecting it downstream

You are not going to catch the build. You have a signed vendor artifact and the only thing you can observe is what it does at runtime. Watch behaviour, not provenance.

The high-value signal is a trusted management agent, running as SYSTEM, spawning a command interpreter. Microsoft's published Defender hunts for this incident were exactly that shape: InitiatingProcessFileName =~ "SolarWinds.BusinessLayerHost.exe" with a child of powershell.exe or cmd.exe.

title: Monitoring platform service spawns a command interpreter
id: 3f0a9c62-5b18-4a3d-9e77-2c41d8b6a5e1
status: experimental
description: >
  Detects a Windows monitoring or management platform service spawning a shell
  or script host. These parents run as SYSTEM and normally do not launch
  interpreters. Extend ParentImage with the agents in your own estate.
references:
    - https://www.crowdstrike.com/en-us/blog/sunspot-malware-technical-analysis/
author: HuntRule
date: 2026-07-31
tags:
    - attack.execution
    - attack.t1059.001
    - attack.t1059.003
    - attack.initial_access
    - attack.t1195.002
logsource:
    category: process_creation
    product: windows
detection:
    selection_parent:
        ParentImage|endswith:
            - '\SolarWinds.BusinessLayerHost.exe'
    selection_child:
        Image|endswith:
            - '\cmd.exe'
            - '\powershell.exe'
            - '\pwsh.exe'
            - '\wscript.exe'
            - '\cscript.exe'
            - '\rundll32.exe'
    condition: selection_parent and selection_child
falsepositives:
    - Orion alert actions configured to run an external program or a script
    - Vendor support scripts and scheduled maintenance jobs
    - Agent self-update routines that shell out
level: high

Feed it Sysmon Event ID 1, which gives you ParentImage, Image and CommandLine. Security Event ID 4688 works if command line auditing is enabled, otherwise you get process names without arguments and the rule loses most of its value.

What it catches is the moment a trusted binary steps outside its normal execution profile. That is the only reliable tell downstream, because everything upstream of it is authentic.

What it misses, plainly:

  • The first 12 to 14 days. SUNBURST slept, and a rule keyed on child processes sees nothing during dormancy.

  • Anything the implant does in-process. SUNBURST profiled the host, checked blocklists and stashed reconnaissance results inside legitimate plugin configuration files. No child process, no alert.

  • DNS-only staging. The C2 was reached through generated avsvmcloud.com subdomains. Pair the rule with Sysmon Event ID 22 and hunt for server processes resolving parent domains they have never resolved before.

  • The compromise itself. This fires after the artifact is already installed under your own change control.

False positives are not theoretical. Monitoring platforms are designed to run scripts, and Orion alert actions can invoke external programs. Baseline your environment before you set this to alert rather than to hunt.

If you run the build server, MsBuild.exe is normal there, which is the problem. Hunt for source files modified inside a solution directory during an active build by a process that is not the VCS client or an editor, and for .bk or .tmp siblings next to tracked sources. That is a hunt, not a detection. An attacker who reads the same report will not reuse the extensions.

ATT&CK mapping

Technique

Name

Where it appears here

T1195.001

Compromise Software Dependencies and Development Tools

Injection at dependency resolution or in the toolchain

T1195.002

Compromise Software Supply Chain

The trojanized signed artifact reaching customers

T1553.002

Subvert Trust Controls: Code Signing

A valid signature on malicious output

T1059.001

Command and Scripting Interpreter: PowerShell

Child process the downstream rule watches for

T1568.002

Dynamic Resolution: Domain Generation Algorithms

Generated C2 subdomains

Summary

A build pipeline compromise puts attacker code into an artifact whose source is clean and whose signature is genuine, which is why the whole downstream verification chain returns green. SUNSPOT is the documented worked example: an implant that swapped InventoryManager.cs in the working tree one second before MSBuild read it. Reproducible builds are the only control here that directly detects that mechanism, SLSA's build track raises the cost of tampering during a build without asserting the source matched the repository, and signing tells you the origin and nothing about the contents. Downstream you cannot verify the build, so detect the runtime behaviour of trusted agents and accept that a patient implant shows you nothing for two weeks.

Collect for this class:
  Sysmon Event ID 1   process creation with ParentImage and CommandLine
  Sysmon Event ID 22  DNS query, keyed on server-role hosts
  Sysmon Event ID 3   outbound network connection by service account processes
  Security 4688       only useful with command line auditing enabled
  Build hosts         file modification events under solution directories

Rules for monitoring agents and management platforms live under /rules?domain=windows, and the broader class is at /rules?q=supply chain. For the wider taxonomy this attack sits inside, start with what is a supply chain attack.

Related articles