NexlinksField Notes from a CTO & CISO
Menu
Practical perspectives on Microsoft technology and hybrid infrastructure. Read the October 2026 newsletter ↗

PRACTITIONER ARTICLE / Security

When a Service Principal Becomes the Attacker: How to Find and Contain Destructive Azure Permissions

Review Azure RBAC, hunt destructive operations and storage key retrieval, and protect recovery resources when a service principal is compromised.

Published
By Phil Kinsley
Microsoft source published:
In this article

Service principals don't tend to receive the same scrutiny as privileged user accounts. They're created to support an application, a deployment pipeline, or an integration, given the permissions needed to function, and often left alone until something breaks. In some environments, those permissions were granted years ago, the original application owner has moved on, and nobody has revisited whether the identity still needs access.

The problem is that a service principal with excessive Azure permissions can be just as destructive as a compromised administrator. It doesn't need to bypass MFA, convince someone to approve a sign-in, or exploit a vulnerability in Azure itself. If an attacker obtains valid credentials, they can operate within whatever permissions that identity already holds.

Microsoft's September 25, 2026 report on Storm-3168 describes destructive Azure activity observed in June 2026. It shows how an attacker can use permissions an organization has already granted.

What happened in the Storm-3168 attack?

Microsoft identified two compromised service principals in an Azure tenant being targeted by Storm-3168, a threat actor also associated with the JADEPUFFER activity previously investigated by Sysdig.

The first service principal spent approximately 15 and a half hours enumerating virtual machines, subscriptions, resource groups, and other Azure resources, completing more than 300 successful read operations. A second compromised service principal performed additional discovery before moving into destructive activity.

In approximately seven minutes, the attacker attempted to delete more than 100 Azure Storage accounts, successfully removing most of those targeted. An Azure Key Vault, Function App, and App Service plan were also deleted, while attempts to remove several SQL databases failed because the attacker used an unsupported API version.

The same identity also tried to remove protections associated with Azure Site Recovery and Azure Backup. Around 30 minutes after the destructive activity ended, it made more than 30 successful requests to retrieve Azure Storage account keys.

One detail from Microsoft's investigation deserves particular attention. The permissions used during the attack weren't the result of an Azure privilege escalation exploit. The compromised identity already had the necessary access through a combination of direct role assignments and group membership.

A Storage Account Contributor assignment granted through a group enabled the destructive storage operations. Direct Contributor permissions allowed the deletion of application resources, and a SQL DB Contributor assignment authorized the attempted database deletions.

Microsoft also found that credentials associated with one of the service principals had previously been exposed in a public GitHub issue. Although the secret was subsequently removed from the issue, it remained visible in the edit history. Microsoft couldn't establish whether that credential was used in the attack, so it would be incorrect to describe the disclosure as the confirmed entry point.

There was no confirmed data exfiltration or ransom note in the Azure activity Microsoft documented. The behavior was consistent with destructive operations that could support ransomware or extortion, but those distinctions are important.

The permissions were already there

The part of this investigation that interests me most is how the attacker used permissions that the organization had already granted.

Consider an application that needs to write files into an Azure Storage account. Rather than granting the application a narrowly scoped data-plane role, someone assigns Storage Account Contributor because it resolves the immediate access problem.

Storage Account Contributor is a management-plane role that allows the identity to manage storage accounts, including operations that can delete them. It also provides the ability to retrieve storage account access keys through the Microsoft.Storage/storageAccounts/listKeys/action permission.

Those account keys are particularly sensitive because, where Shared Key authorization is permitted, they can provide access to the storage account's data regardless of whether the identity was separately granted a Storage Blob Data Reader or Contributor role.

That introduces two separate concerns. The identity may have sufficient permissions to delete the storage account itself, while access to its keys may allow an attacker to read or modify the data stored within it.

The distinction between Azure management-plane and data-plane permissions is important here. Removing broad administrative assignments without reviewing key retrieval permissions, application authentication methods, and storage configuration can leave a substantial amount of exposure in place.

Start by reviewing your service principals

Before introducing additional security products, I'd start by understanding which application identities exist and what they can actually do.

In the Microsoft Entra admin center, navigate to Entra ID > Enterprise applications > All applications. Review the service principals associated with internally developed applications, automation, integrations, and deployment tools.

For each identity, establish who owns it, whether the application is still being used, how it authenticates, and whether its Azure role assignments still reflect its operational requirements.

You can use Azure CLI to inspect the identity and its directly assigned Azure RBAC roles.

az account set --subscription "<subscription-id>"

SP_OBJECT_ID="<service-principal-object-id>"

az ad sp show --id "$SP_OBJECT_ID" \
  --query "{name:displayName,appId:appId,objectId:id}" \
  --output table

az role assignment list \
  --assignee "$SP_OBJECT_ID" \
  --all --include-inherited \
  --query "[].{role:roleDefinitionName,scope:scope}" \
  --output table

Use the service principal object ID from Enterprise applications, not the application registration object ID. This example uses Bash syntax, such as Azure Cloud Shell in Bash mode. The operator needs permission to read the service principal and the relevant role assignments. The --all option includes assignments at scopes below the selected subscription; it does not enumerate every subscription in the tenant. Repeat the review for each relevant subscription, and check management group assignments where applicable.

There's an important limitation to this approach. A direct role assignment query shouldn't be treated as a complete effective-permissions assessment. Service principals can receive permissions through group membership, as happened in the Storm-3168 investigation. Review group memberships, assignments granted to those groups, inherited scopes, and any relevant custom role definitions separately.

Pay particular attention to Contributor, Owner, Storage Account Contributor, and other roles that permit destructive management operations or retrieval of sensitive credentials. The role's name is a starting point, but its actual Actions and DataActions determine what the identity can do.

For every broad assignment, ask whether the application needs to delete infrastructure, retrieve storage keys, or modify recovery resources.

If the answer is no, that permission should be reviewed and removed through the organization's change process.

Detecting destructive Azure operations with KQL

The next area I'd examine is monitoring. Azure Activity logs capture management-plane operations and provide a useful starting point for identifying suspicious resource deletion, changes to management controls, and storage account key retrieval.

Azure Activity logs record Azure Resource Manager management operations. They do not provide a complete record of read-only discovery or storage data access. The logs are collected by the platform, but to run the following queries in Log Analytics or Microsoft Sentinel, the relevant subscriptions must have their Activity logs exported to a Log Analytics workspace. Verify that the AzureActivity table contains data before building detections around it.

The following query looks for identities performing multiple successful or failed delete operations within a ten-minute window.

AzureActivity
| where TimeGenerated >= ago(7d)
| where CategoryValue =~ "Administrative"
| where OperationNameValue contains "/delete"
| where ActivityStatusValue in~ ("Succeeded", "Failed")
| summarize
    SuccessfulDeletes = countif(ActivityStatusValue =~ "Succeeded"),
    FailedDeletes = countif(ActivityStatusValue =~ "Failed"),
    Resources = dcount(ResourceId),
    SampleResources = make_set(ResourceId, 5)
    by Caller, CallerIpAddress, bin(TimeGenerated, 10m)
| where SuccessfulDeletes >= 5 or FailedDeletes >= 10
| order by SuccessfulDeletes desc

Use this as a hunting query. The thresholds are examples, and legitimate cleanup or deployment pipelines can trigger them. The query counts terminal activity-log records, not guaranteed unique API requests, and dcount estimates distinct resource IDs. Inspect OperationId and EventDataId when checking for duplicate records.

The important work comes from establishing which identities normally perform those operations, which resources they manage, and whether the observed behavior aligns with an approved change.

I'd also pay close attention to attempts to delete resource locks, particularly around backup and recovery infrastructure. A failed attempt can still be significant if the identity has no legitimate reason to modify those protections.

Monitor storage account key retrieval

The successful ListKeys requests were another significant part of Microsoft's findings.

The following query identifies callers making successful storage account key retrieval requests across multiple accounts within an hour.

AzureActivity
| where TimeGenerated >= ago(7d)
| where OperationNameValue contains
    "Microsoft.Storage/storageAccounts/listKeys/action"
| where ActivityStatusValue =~ "Succeeded"
| summarize
    SuccessfulRequests = count(),
    Accounts = dcount(ResourceId),
    SampleAccounts = make_set(ResourceId, 10)
    by Caller, CallerIpAddress, bin(TimeGenerated, 1h)
| where Accounts >= 3
| order by SuccessfulRequests desc

An application retrieving keys for several storage accounts isn't automatically malicious. However, an identity that normally works with one account suddenly requesting keys across several subscriptions deserves investigation, especially if the same identity has recently attempted resource deletions.

Both queries group by caller and source IP. Activity spread across IP addresses can appear in separate results. The time bins are fixed clock-aligned intervals, so a burst crossing a bin boundary can fall below the example threshold. A scheduled rule also needs a deliberate execution interval, lookback, suppression, and investigation process.

Caller can represent a user or an application identity. Correlate the event's claims and authorization details with Entra records before attributing it to a service principal. Neither query proves compromise, successful data access, or exfiltration.

I'd correlate the results with Microsoft Entra service principal sign-in logs, particularly the source IP addresses, credential types, service principal IDs, and applications being accessed. If Entra diagnostic settings are configured to export those events, the AADServicePrincipalSignInLogs table provides another useful source for investigation.

Microsoft documents Activity log events as typically available within 3 to 20 minutes of the event. Export and query availability can add further delay. In an attack where substantial destruction occurred in seven minutes, relying on log-based detection alone could mean identifying the activity after considerable damage has already occurred.

That makes preventive controls just as important as the hunting rules.

Reducing the impact of a compromised workload identity

There are several controls I'd prioritize, although not all will be appropriate for every application.

Replace long-lived credentials where possible

For workloads running on supported Azure services, managed identities should generally be preferred over application secrets. They remove the need for application developers and administrators to distribute and manually rotate credentials.

For workloads outside Azure, workload identity federation may provide a similar benefit where the external identity provider and application support it.

Where certificates or client secrets remain necessary, establish ownership, expiration monitoring, rotation procedures, and controls that prevent credentials from being exposed in source code, deployment files, tickets, and troubleshooting output.

Moving a secret into Key Vault improves how it's stored and accessed, but it doesn't resolve excessive permissions granted to the application using that secret.

Review Conditional Access for workload identities

Microsoft Entra Conditional Access can apply location-based and risk-based restrictions to supported service principals, helping prevent tokens from being issued under conditions that shouldn't be permitted.

However, these policies aren't identical to user-based Conditional Access policies.

They require the appropriate Microsoft Entra Workload ID Premium licensing, apply to supported single-tenant service principals, and don't cover managed identities. The service principals must also be targeted directly by the policy rather than relying on group membership.

I'd begin in report-only mode, validate normal authentication patterns, and make sure an approved service or integration won't be disrupted before enforcing restrictions.

Remove unnecessary storage key access

If an application only needs to read or write blobs, look at Azure Storage data-plane RBAC roles instead of giving it broad storage management permissions.

Where supported, disable Shared Key authorization after validating application dependencies. For covered services and requests, this prevents authorization using the account keys even if those keys are obtained. It does not remove listKeys permission, stop resource-management deletion, or invalidate Entra-authorized access. User delegation SAS for Blob Storage is Entra-authorized and is not blocked by this setting.

There are compatibility implications, particularly for account SAS tokens, service SAS tokens, some Azure Files scenarios, and applications that still depend on storage account keys. This isn't a setting I'd recommend changing across every storage account without first understanding those dependencies.

Protect the recovery environment

Microsoft observed that resource locks and storage account-level deletion protections prevented some destructive operations during the Storm-3168 attack.

That's encouraging, but Azure resource locks have limitations. They protect Azure Resource Manager operations and don't prevent someone with appropriate data-plane access from deleting individual blobs or other stored data.

Locks can also be removed by identities with the necessary lock management permissions.

For critical recovery infrastructure, I'd look at immutable backup protections, enhanced soft delete, and Azure Backup multi-user authorization using Resource Guard. The objective is to keep destructive operations against recovery assets under separate authorization, ideally outside the same administrative boundary as the production resources being protected.

These controls need to be tested against operational requirements because poorly scoped locks and backup restrictions can interfere with legitimate maintenance.

Use Defender for Cloud where the workload warrants it

Microsoft Defender for Resource Manager monitors Azure management operations for suspicious behavior, while Defender for Storage and Defender for Key Vault provide additional protection relevant to the resources targeted in this investigation.

I'd assess the relevant Defender for Cloud plans against the organization's assets and risk profile rather than enabling every available plan without considering cost, coverage, and existing monitoring.

The detections are useful, but they don't replace appropriate role assignments, credential management, recovery protection, or an incident response process.

What I'd check following a suspected compromise

If a service principal is suspected of compromise, changing its secret is only part of the response.

First, establish the identity's effective permissions, review its authentication activity, and identify every subscription and resource group it could access. Preserve the relevant logs and investigate the operations performed before containment, including unsuccessful attempts that may indicate the attacker's intended targets.

Containment may require disabling the service principal, removing its assignments, or revoking and replacing credentials, depending on the application and the evidence. Existing access tokens and application dependencies need to be considered, so credential rotation shouldn't be assumed to terminate every active session immediately.

If storage account keys may have been exposed, review access to the affected accounts and plan key rotation or other containment accordingly. Investigate resource deletion, changes to backup protections, and any evidence of data-plane access separately.

The broader investigation should also consider whether the same credentials were used elsewhere, whether additional secrets were exposed, and whether the attacker introduced new identities or access paths.

My recommendation

The Storm-3168 investigation is a useful reminder that cloud infrastructure can be compromised through identities that are doing exactly what their permissions allow.

Microsoft's findings also show that independent protections can make a difference. Some resources survived because deletion safeguards were in place, while other resources were removed using permissions that had already been granted to the compromised identity.

Start with service principals holding Contributor, Storage Account Contributor, or similarly broad roles at subscription or management-group scope. Include assignments inherited through groups. Confirm the application owner, required operations, credentials, and monitoring before reducing access through a tested change.

I'd also test whether a compromised application identity could interfere with the organization's ability to recover. Backups are valuable, but they become a much less reliable safeguard if the same compromised identity can administer the application, delete its storage, and interfere with recovery protections.

The most useful question to come out of this investigation isn't whether your Azure environment has service principals with elevated permissions. Most organizations will find that it does.

It's whether you know which of those identities could cause serious damage if their credentials were compromised, and what would actually prevent them from doing so.

Primary source

Storm-3168: Agentic-driven cloud attacks using compromised service principals ↗

Further sources

JADEPUFFER: Agentic ransomware for automated database extortion ↗

List Azure role assignments using Azure CLI ↗

Azure built-in roles for Storage ↗

Azure Monitor Logs reference: AzureActivity ↗

Activity Log in Azure Monitor ↗

Prevent Shared Key authorization for an Azure Storage account ↗

Conditional Access for workload identities ↗

Lock your Azure resources to protect your infrastructure ↗

Secure your Azure Backup deployment ↗

Overview of Microsoft Defender for Resource Manager ↗

Managed identities for Azure resources ↗

Continue reading

You’ve Secured Your AI Agents. But Can You Trust Their MCP Servers? ↗

Managing Windows Server Security with Intune, Defender, and Azure Arc ↗

Are your security investments actually reducing risk? ↗