Anatomy of a Supply Chain Attack: How Behavioral Detection Caught What Signatures Missed
Back to Blog

Anatomy of a Supply Chain Attack: How Behavioral Detection Caught What Signatures Missed

Supply chain intrusions have become the signature technique of the past decade: NotPetya via a Ukrainian tax-software update, SolarWinds Orion, Kaseya VSA, 3CX, XZ Utils. Each time, defenders learn the same lesson the hard way. A signed binary from a trusted vendor is not a guarantee; it is a starting position. What follows is a sanitised account of a real incident where our managed XDR caught a vendor-supplied update executing attacker-controlled code within minutes, contained the blast radius before any exfiltration occurred, and let a Nordic logistics operator resume operations the same morning.

The Environment

The customer runs a typical mid-sized enterprise stack: Windows 11 endpoints under Intune, hybrid Active Directory joined to Entra ID, two Azure regions, a warehouse OT segment, and a fleet of ruggedised Android devices for drivers. They route all security telemetry into our managed XDR: endpoint EDR on every host, NDR sensors at three transit points, cloud audit from Azure and Microsoft 365, identity telemetry from Entra ID, plus DNS and firewall logs. Roughly 4.2 million events per day land in the analytics fabric. A 24/7 Norwegian SOC runs on top.

What the Platform Saw, Minute by Minute

The adversary reached in through a legitimate build-automation vendor whose update agent was installed on roughly 60 of the customer's endpoints. The intrusion unfolded as follows.

T+00:00: ingestion

02:14:07 UTC. The vendor's update service, running as SYSTEM on a warehouse file server, contacts its usual update endpoint. This happens every four hours and has happened for two years. The HTTPS request is indistinguishable from every prior one.

T+00:08: first deviation

02:14:15 UTC. Instead of fetching a single signed payload, the update service performs a second DNS lookup, this time for a domain that had been registered 41 hours earlier. The DNS sensor flags the lookup against our newly-observed-domain feed. On its own, one NXDOMAIN-adjacent query per process is barely noise.

T+00:14: correlation fires

02:14:21 UTC. The endpoint agent observes the vendor update service spawning powershell.exe with base64-encoded arguments and the -NoProfile -EncodedCommand switches. The XDR engine correlates three weak signals into one strong incident:

  • Unknown child process lineage (baseline says this service never spawns PowerShell)
  • DNS resolution to a domain younger than 72 hours
  • Script block logging shows a staged downloader pattern with in-memory reflection

The composite score clears the escalation threshold. Incident #IR-2025-0128 is opened automatically and assigned to the on-call analyst.

T+00:19: SOC paged

02:14:26 UTC. PulseGuard routes the incident to our on-call and escalation engineers. The incident packet contains the process tree, decoded PowerShell, the DNS event, the destination IP (matching an ASN we had previously flagged for commodity-loader infrastructure), and a pre-drafted containment playbook.

T+00:23: containment begins

02:14:30 UTC. The analyst confirms the detonation is live. Automated playbooks execute in parallel: the affected endpoint is network-isolated via the EDR agent; the firewall ingests the malicious FQDN and IP as blocks; the DNS resolver null-routes the domain; the update agent's service account is forced to re-authenticate; and the compromised host's disk and memory are captured for forensics.

T+03:47: full blast radius mapped

02:17:54 UTC. Cross-referencing the vendor update service's signature across all 60 hosts that had it installed shows that six of them had already executed the second-stage downloader, but none had completed the third stage. All six are isolated. The customer's IT leadership is now actively engaged in the incident response.

T+14:12: adversary out

02:28:19 UTC. No sessions remain established to the attacker's infrastructure. No credentials were staged. No data left the network. Forensics confirms the adversary had a foothold for roughly 14 minutes on six endpoints and never progressed past reconnaissance.

What the Attack Actually Was

Upstream investigation (coordinated with the vendor and with CERT-EU) revealed that the vendor's build pipeline had been compromised roughly nine days earlier. An attacker with access to a GitLab CI runner had injected a small loader into the vendor's update payload. The loader ran only when two conditions were met: the target was domain-joined, and the hostname did not start with a string that excluded the vendor's own QA environment. Every shipped update was correctly signed with the vendor's code-signing certificate because the injection happened before the signing step.

This is exactly why pure signature-based trust fails: the binary was valid, the certificate was valid, the issuer was valid, and the update channel was the genuine one.

Mapping to MITRE ATT&CK

  • Initial Access: Supply Chain Compromise: Compromise Software Supply Chain (T1195.002)
  • Execution: Command and Scripting Interpreter: PowerShell (T1059.001)
  • Defense Evasion: Signed Binary Proxy Execution (T1218) and Obfuscated Files or Information: Command Obfuscation (T1027.010)
  • Command and Control: Application Layer Protocol: DNS (T1071.004) plus Dynamic Resolution: Fast Flux DNS (T1568.001)
  • Discovery: System Information Discovery (T1082), Domain Trust Discovery (T1482)
  • Credential Access: Staged but not executed (T1003 candidate payload found in memory)

The Detection Logic in Plain English

There was no malware hash to match. There was no IP address on a block-list. The certificate was valid. The domain was technically new but not on any feed at the time. The only way this detection worked was by correlating things that, individually, a human analyst would never have had time to look at:

  1. A trusted long-running service spawning a scripting host it had never spawned before, across a fleet-wide behavioural baseline.
  2. A DNS query to a newly-observed domain from a SYSTEM-level process at an unusual hour.
  3. Outbound connectivity to an ASN flagged by our threat-intel fabric, even though the specific IP was unknown.
  4. A PowerShell invocation whose script block matched a family of reflective loaders, scored by ML rather than hash.

Any one of these signals in isolation is noise. The XDR platform's job is to notice that all four fired on the same process tree within 14 seconds and call that an incident, not a log entry.

What Happened in the Following 72 Hours

Inside the customer's environment we rotated the vendor's service-account credentials, re-issued all secrets stored on the six affected hosts, reset their local machine accounts, and added a fleet-wide EDR rule that blocks the vendor's update service from spawning any scripting host for the next 30 days while the vendor reissues their pipeline.

Upstream, our detection and the forensic package were shared with the vendor and with two other managed-security shops that run the same telemetry. Within 24 hours the compromised runner was retired, the signing key was rotated, and a clean update was pushed. Our customer was among the first operators to receive an advisory and a clean build because we held the earliest forensic evidence.

Hardening That Survives the Next One

Every supply chain incident reveals the same structural gaps. The recommendations that came out of this one, which we have since folded into our standard hardening playbook:

  • Assume signed does not mean safe. Behavioural baselining on every long-running privileged service, with alerting on any child-process deviation, is now table stakes.
  • Egress is a product, not a feature. Explicit DNS allow-lists for automation accounts, with alerting on newly-observed domains, catches loaders before payloads land.
  • Identity is the new perimeter, still. Vendor service accounts should be treated as first-class identities with conditional access, token lifetime limits, and session-risk scoring.
  • Script-block logging, everywhere. PowerShell (and cmd.exe, and wscript, and every automation shell) should emit full command text into the telemetry fabric. Redaction at the analyst layer is cheap; retroactive forensics without logs is not.
  • Test the response, not just the defence. The four-minute containment time on this incident was the product of tabletop drills run on the customer's actual topology. Playbooks written in PDFs are not playbooks.

Why This Kind of Detection Needs the Whole Fabric

This detection was only possible because endpoint telemetry, DNS logs, identity events, and threat-intel context all landed in the same query plane and were evaluated by the same correlation engine. A siloed SIEM would have captured each signal but missed the relationship. An EDR without network context would have seen a PowerShell spawn and scored it benign because the parent was a signed service. A NDR without endpoint context would have seen a DNS query to a new domain and shrugged.

The value of XDR as a model, and the reason we run it as a managed service for our customers, is that the correlation is not an analyst's job anymore. The correlation is the platform. The analyst's job is to decide what the correlation means and how to respond. That split is where four-minute containment comes from.

If You Operate in Norway and Read This Far

Our SOC runs from Norway, reports in Norwegian, answers to Norwegian data-residency rules, and holds customer telemetry on infrastructure we operate ourselves. If you want the same detection fabric applied to your environment, reach out and we will get you sorted.

Subscribe to our newsletter

Stay in touch and keep up to date with our latest company news and relevant updates.
  • Thank you, check your inbox

    Thank you for subscribing, we have sent you an email, please click the link in the email to confirm your subscription.

©2026 ZeroSubnet AS  ·  Org. nr. 923 669 442
Leif Tronstads plass 6, 1337 Sandvika