Skip to content

This tool is not affiliated with, endorsed by or sponsored by Microsoft Corporation. Azure, Microsoft Azure and Microsoft Defender for Cloud are trademarks of the Microsoft group of companies. Other names are trademarks of their respective owners.

An Azure breach, walked through (fictional case)

A fictional Azure breach investigated end to end: leaked app secret, self-assigned role, Run Command, stolen managed identity token, Key Vault and blob theft.

Published on 7 min read

TL;DR. This case is fictional. Kestrel Freight does not exist; its tenant, identities and IP addresses (RFC 5737 documentation ranges) are invented, and the logs were generated in the real Azure export formats for the analyzer's Try a sample button. In 41 minutes on a Sunday night, a leaked CI/CD service principal grants itself Contributor, runs a script on a VM, replays the VM's managed identity token, adds itself to a Key Vault access policy, reads all 12 secrets, mints an account SAS, downloads 330 database backups (about 48.6 GB), then deletes the vault's diagnostic setting and the Activity Log export. The analyzer returns Compromised with 11 high-severity findings.

Real incidents are messy, partial and confidential. A synthetic one lets me show every step with the evidence in front of you. Load it yourself: open the Azure Forensics analyzer and click Try a sample. The page shows a banner reminding you it is the fictional sample.

The setup

Kestrel Freight (fictional) runs a web portal on vm-app-01 in rg-prod-app. The VM has a system-assigned managed identity that reads its database connection string from Key Vault kv-kestrel-prod every hour and its settings from storage account stkestrelbackups, where nightly database backups are also written. A CI/CD service principal, sp-gh-deploy, deploys the portal every morning from two runner IPs. Over-privileged for convenience, it holds User Access Administrator.

The export covers 2026-09-07 to 2026-09-14: a week of normal activity (the learning period), then the incident. It contains:

File / folderSourceFormat
insights-activity-logs/…/PT1H.jsonActivity LogStorage export, JSON Lines
activity-log-az-cli.jsonActivity Log, incident dayaz monitor activity-log list (REST schema)
insights-logs-auditevent/…Key Vault AuditEventJSON Lines
insights-logs-storageread/…, …storagewrite/…Blob logsJSON Lines
insights-logs-flowlogflowevent/…VNet flow logs{"records": […]}
insights-logs-networksecuritygroupflowevent/…NSG flow logs v2{"records": […]}
defender-alerts.jsonDefender for CloudREST {"value": […]}

The storage export and the CLI export overlap on the incident day: the analyzer merges 11 duplicate events.

Coverage first

All five sources are present. The Activity Log has 99 events, Key Vault 186, storage 509, flows 88, Defender 1. The learning period is long enough, so the first-seen rules run. Good: a verdict on this data is meaningful for both control and data plane.

The timeline, minute by minute (UTC, 2026-09-14)

TimeEventSourceFinding(s)
02:14:05sp-gh-deploy calls ARM from 203.0.113.66 — never seen before — and assigns Contributor on the subscription to itselfActivity LogAZ-ID-001, AZ-RBAC-002, AZ-RBAC-003
02:19:40runCommand/action on vm-app-01, same SP, same IPActivity LogAZ-VM-001
02:20:05VM 10.10.1.4 connects out to 203.0.113.66:80 (812 B sent, 46,338 B received), then :443VNet flow logAZ-NET-004
02:22:10The VM's managed identity reads db-conn-string from 203.0.113.66 with curl/8.5.0Key VaultAZ-ID-002
02:23:30SP writes the vault's access policy (secrets get / list for itself)Activity Log + Key VaultAZ-KV-003
02:24:10–02:25:14SecretList, then 12 distinct SecretGet by the SP, Python SDKKey VaultAZ-KV-001, AZ-KV-002
02:25:03Defender for Cloud alert on the VM (generated time; the alert's start time is 02:21)DefenderAZ-DEF-001
02:31:02listAccountSas/action on stkestrelbackups: blob, read + list, valid 7 daysActivity LogAZ-ST-001
02:33:00–02:51:29330 distinct backup blobs downloaded with that SAS from 203.0.113.66, AzCopy user agent, ~48.6 GBStorageAZ-ST-005
02:55:12Key Vault diagnostic setting kv-audit-to-storage deletedActivity LogAZ-DE-001
02:55:40Subscription diagnostic setting export-activity-to-storage deletedActivity Log (CLI export only)AZ-DE-002

A few things worth noticing, because they generalise to real cases.

The first event is the self-assignment, not the sign-in. The Activity Log starts where the attacker first touched ARM. How the SP's secret leaked is an Entra ID question (service principal sign-ins, credential changes) for m365forensics.com.

The managed identity token appears two minutes after Run Command. The script queried the Instance Metadata Service and sent the token out. The only visible trace is the same object ID, bound to vm-app-01 by its xms_mirid claim, calling Key Vault from the attacker's IP instead of the VM's 10.10.1.4. That is AZ-ID-002, explained in managed identity abuse.

The access-policy escalation needs no extra role. Contributor was enough to add an access policy. See Key Vault audit logs.

The SAS is the principal of the download. The 330 GetBlob requests carry no user or app ID, only SAS authentication and the SAS signature hash. The analyzer shows them as one sas:… principal with one IP and a byte total (storage exfiltration).

The Activity Log export dies at 02:55:40, the Activity Log does not. The storage-exported Activity Log has nothing after 02:55:40. The deletion of the export itself is only visible because the investigator also ran az monitor activity-log list, which reads Azure's own 90-day copy (defense evasion).

One honest caveat about the sample: its Run Command event includes the request body with the script. Real Activity Log entries for Run Command generally do not include the script contents, as Mandiant's research shows; you would recover it from the VM (Run Command attacks).

The verdict

Eleven high-severity findings (AZ-RBAC-002, AZ-RBAC-003, AZ-VM-001, AZ-NET-004, AZ-ID-002, AZ-KV-001, AZ-KV-002, AZ-ST-001, AZ-ST-005, AZ-DE-001, AZ-DE-002) and three medium (AZ-ID-001, AZ-KV-003, AZ-DEF-001): Compromised. There is also one low finding from the learning week: the platform admin listing the storage account keys on 2026-09-09 (AZ-ST-002). It is legitimate, and the verdict ignores it — a useful reminder that low findings are context, not accusations.

The entity view

Pivoting on 203.0.113.66 in the Entities tab gathers every event from that address: the SP's ARM calls, the managed identity's Key Vault read, the SP's secret reads and the SAS downloads. The IP's row also carries the flow totals from the VNet flow logs. That one filter is the incident.

Pivoting on the SP's object ID shows the contrast with its baseline: a week of daily 10:00 deployments from two runner IPs, then a 41-minute burst from a new IP doing things it never did.

The remediation checklist, as generated

The Remediation tab orders the steps from the findings:

  1. Remove the role assignments created during the incident; review who holds Owner / User Access Administrator.
  2. Rotate the SP's credentials and revoke sessions; investigate the identity side in Entra ID.
  3. Isolate vm-app-01, snapshot its disks, rebuild it; review extensions and Run Command history.
  4. Block 203.0.113.66 and hunt for it elsewhere.
  5. Rotate all 12 secrets read from kv-kestrel-prod; move the vault to Azure RBAC; remove the rogue access policy.
  6. Rotate both storage account keys (this kills the SAS); establish exactly which backups left, for notification.
  7. Restore the deleted diagnostic settings and protect them with Azure Policy.
  8. Investigate the Defender alert.

What this case does not show

  • Initial access. The secret leak is outside these logs.
  • What the script did on the VM beyond the network flows and the token use. That needs the disk.
  • Anything after 02:55:12 in Key Vault. The vault's logging is gone from that moment; if the attacker came back at 03:10, the Key Vault log would not say.

For the process around a case like this, start from the incident response guide; for the tool itself, the step-by-step guide.

Related articles

Azure Activity Log retention is 90 days and resource logs are off by default. What each Azure log cannot tell you, and how to write an honest conclusion.
Step-by-step Azure Activity Log analysis with the free Azure Forensics analyzer: load exports, read the verdict, findings, timeline and entities, export.
Think your Azure subscription is compromised? Which logs to preserve first, the operations that betray an attacker, and how to go from Activity Log to verdict.

This tool is not affiliated with, endorsed by or sponsored by Microsoft Corporation. Azure, Microsoft Azure and Microsoft Defender for Cloud are trademarks of the Microsoft group of companies. Other names are trademarks of their respective owners.