Managed identity and service principal abuse in Azure
How attackers abuse Azure service principals and managed identities: leaked secrets, IMDS token theft, federated credentials, runbooks, and the log signals.
TL;DR. Non-human identities are the quiet majority of Azure callers, and the favourite disguise of intruders. Three patterns: a service principal whose secret leaked (CI/CD variables, a repo, a laptop) used from a new IP; a managed identity token taken from the VM's Instance Metadata Service (169.254.169.254) and replayed from elsewhere; and persistence through federated credentials, Automation runbooks, Logic Apps or Functions running as a privileged identity. In the logs, the xms_mirid claim marks a managed identity, and "same identity, new IP" is the strongest single signal you have.
When I review a compromised subscription, the human accounts are usually easy: few of them, predictable hours, MFA in the claims. The hard part is the dozens of application identities that call Azure Resource Manager all day. That is where attackers blend in.
Service principals and managed identities, in log terms
| Identity | What it is | How it appears in Activity Log claims |
|---|---|---|
| Service principal (app registration) | Application identity authenticated with a client secret, certificate or federated credential | appid + object ID, often idtyp: app; caller is a GUID |
| Managed identity | Service principal managed by Azure, bound to a resource (system-assigned) or standalone (user-assigned) | Same, plus xms_mirid = resource ID of the VM / app / identity it belongs to |
| User | Human account | UPN, name, amr (e.g. pwd, mfa) |
The Azure Forensics analyzer classifies callers this way (claims first, then principalType, then GUID-shaped callers) and shows the type in the Entities tab. In Key Vault and storage logs the same claims appear under identity.claim and identity.requester.
Pattern 1: a leaked service principal secret
The initial access is outside Azure: a secret committed to a repository, printed in a CI log, or stolen from a developer's machine. Microsoft Entra ID sees the sign-in (service principal sign-in logs); Azure sees what follows.
Signals in Azure logs:
- The SP calls ARM from an IP it never used before. CI runners have a small, stable set of IPs or a known cloud range; an attacker's VPS is neither.
- The SP does something it never did: role assignments, Run Command,
listKeys, access-policy changes. - Timing: deployment SPs run when pipelines run. A burst at 02:14 on a Sunday with no pipeline run is telling.
MITRE ATT&CK: T1078.004 Cloud Accounts. How the secret leaked, and whether new secrets were added to the application (T1098.001 Additional Cloud Credentials), is recorded in the Entra ID audit log — analysed by the sibling tool m365forensics.com.
Pattern 2: managed identity token theft via IMDS
Any process on a VM can ask the Instance Metadata Service for a token of the VM's managed identity:
GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
Metadata: true
Microsoft states the security boundary plainly: "All code/scripts running on a virtual machine can request and retrieve tokens for any managed identities available on it" (Use managed identities on a VM to acquire an access token). The Metadata header is an SSRF mitigation, not an authorization check.
So code execution on a VM — through Run Command, a web shell or an SSRF that can set headers — becomes a cloud identity. The attacker copies the bearer token and uses it from their own machine until it expires (T1528 Steal Application Access Token).
The signal is simple and strong: a managed identity used from an IP that is not its host. A VM's managed identity normally reaches Key Vault and storage from the VM's private IP (through a service endpoint or private endpoint) or from a stable outbound public IP. The same object ID showing up with a residential or VPS address, and a curl user agent, is token replay.
Pattern 3: persistence through identities
Once an attacker has a privileged identity, they want one that survives password resets and secret rotation:
| Persistence | Activity Log operation | MITRE |
|---|---|---|
| Federated credential on a user-assigned managed identity (lets an external IdP get tokens for it) | Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/write | T1098.001 |
| Automation runbook or webhook, running as the Automation account's identity | Microsoft.Automation/automationAccounts/runbooks/write, …/webhooks/write, …/jobs/write | T1648 |
| Logic App or Function code changed | Microsoft.Logic/workflows/write, Microsoft.Web/sites/functions/write, function keys written | T1648 |
| New secret or certificate on an app registration | Entra ID audit log, not the Activity Log | T1098.001 |
Federated credentials are legitimate and common (GitHub Actions, AKS workload identity). The question is always: which issuer and subject, created by whom, and does anyone own it?
What the analyzer flags
| Rule | Detects | Level |
|---|---|---|
AZ-ID-001 | Service principal calls ARM from an IP not seen during the 72-hour learning period | Medium |
AZ-ID-002 | Managed identity used from a new IP — Activity Log, Key Vault or storage | High |
AZ-RBAC-002 | Service principal or managed identity assigned Owner, Contributor or an access-management role | High |
AZ-PER-001 | Automation runbook, webhook or job created | Medium |
AZ-PER-002 | Logic App or Function code changed | Low |
AZ-PER-003 | Federated credential added to a managed identity | High |
"New IP" rules need history: with less than 72 hours of logs before the suspicious activity, they cannot run, and the coverage block says so. Known false positives: CI runners with changing IPs, VMs redeployed with a new private IP.
Triage checklist
- List every non-human identity with a finding in the Entities tab, then open each one's events.
- For each, establish the baseline: normal IPs, normal operations, normal hours. The first deviation is the start of your timeline.
- For a managed identity: which resource is it bound to (
xms_mirid)? Was there code execution on that resource shortly before (Run Command, extension, deployment)? - For a service principal: when was its last credential added, and by whom? (Entra audit log.)
- What did it touch? Key Vault reads, storage reads, role assignments. Each is a new branch of the investigation.
Remediation
- Rotate the secrets and certificates of compromised service principals; remove credentials nobody owns.
- A managed identity has no secret to rotate. Contain the host (isolate and rebuild the VM), and reduce the identity's role assignments. Access tokens already stolen stay valid until they expire, so keep watching the identity's activity from foreign IPs for a while — and rotate the secrets it could read.
- Remove unknown federated credentials, runbooks and webhooks.
- Reduce privileges: no subscription-wide Contributor for a web app's identity.