Threat Overview
Microsoft Security Research published a detailed September 25, 2026 account of Storm-3168, linked to the JADEPUFFER activity first documented by Sysdig. In an early-June incident, two compromised service principals in one Azure tenant divided work between reconnaissance and destructive/credential-collection activity. One identity performed more than 300 successful reads over roughly 15.5 hours. The second later enumerated App Service configuration, then generated more than 150 destructive or credential-related operations in 35 minutes. A seven-minute burst included more than 100 storage-account deletion attempts; most targeted accounts were deleted. The actor also deleted a Key Vault, Function App, and App Service plan, attempted Azure SQL database deletion, targeted Site Recovery and Backup protection locks, and performed more than 30 successful ListKeys operations.
Microsoft confirmed the activity and assessed its destructive and recovery-inhibiting behavior as ransomware-aligned, but did not observe a ransom note or confirm successful exfiltration. The organization had previously exposed the service principal’s client ID, client secret, and tenant ID in a public GitHub issue; Microsoft could not confirm that this specific secret enabled the intrusion. Automation is a high-confidence researcher assessment based on concurrent tokens, rapid sequences, repeated probing, and division of work. Observed requests used python-requests/2.34.2; three published IPs are included below.
References
- Microsoft Security Blog — Storm-3168: Agentic-driven cloud attacks using compromised service principals — September 25, 2026.
- The Hacker News — JADEPUFFER-linked attackers used compromised service principals — September 28, 2026.
Impacted Systems
- Vendor/platform: Microsoft Azure and Microsoft Entra workload identities/service principals.
- Deployment: Customer Azure tenants; the observed incident crossed two subscriptions.
- Targeted resources: Azure Storage accounts and access keys, Virtual Machines, subscriptions/resource groups, App Service configuration, Key Vault, Function Apps, App Service plans, Azure SQL databases, Site Recovery locks, and Azure Backup protection locks.
- Prerequisite: A compromised service principal with sufficient Azure RBAC rights. Observed effective roles included group-granted Storage Account Contributor and direct Contributor and SQL DB Contributor assignments.
- Exposure condition: Long-lived workload secrets exposed publicly or otherwise stolen; public App Services were also probed, but Microsoft did not establish an App Service-to-ARM credential path for the impacted tenant.
- Versions: Cloud control-plane activity is not tied to a product build. Resource locks and storage deletion protection blocked some deletion attempts.
Why this matters
The campaign converts a compromised non-human identity into rapid tenant-scale resource discovery, credential access, data destruction, and recovery impairment. The observed operation used ordinary Azure APIs and valid RBAC, so prevention and detection depend heavily on workload-identity hygiene and behavioral control-plane analytics rather than malware signatures.
Exploitation Status
Microsoft directly observed and investigated the destructive Azure incident. Published facts include two compromised service principals, 300+ reads, 150+ destructive/credential operations, 100+ storage-deletion attempts, resource deletions, lock-deletion attempts, and successful key retrievals. Initial access remains unconfirmed. The earlier JADEPUFFER/Langflow agentic-ransomware activity provides context, but this hunt focuses on Microsoft’s confirmed Azure control-plane observations.
What this hunt looks for
Published infrastructure, unusual service-principal sign-ins, high-volume resource discovery, App Service configuration reads, storage-key retrieval, deletion bursts, recovery-lock tampering, and tightly sequenced discovery-to-destruction.
Required logs
AzureActivity is required for Azure Resource Manager behavior. AADServicePrincipalSignInLogs is required for workload-identity authentication. AzureDiagnostics or resource-specific App Service logs and Microsoft Defender for Cloud alerts add coverage.
Hunt 1 — High-likelihood published infrastructure across Azure control-plane logs
let Storm3168IPs = dynamic(["45.131.66.106","34.153.223.102","64.20.53.230"]);
AzureActivity
| where TimeGenerated > ago(30d)
| extend SrcIp = tostring(coalesce(CallerIpAddress, tostring(parse_json(Properties).clientIpAddress)))
| where SrcIp in (Storm3168IPs)
| project TimeGenerated, SubscriptionId, Caller, SrcIp, OperationNameValue, ActivityStatusValue, ResourceGroup, ResourceId, Properties
| order by TimeGenerated desc
Hunt 2 — Service-principal sign-ins from published IPs or observed user agent
let Storm3168IPs = dynamic(["45.131.66.106","34.153.223.102","64.20.53.230"]);
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where IPAddress in (Storm3168IPs) or UserAgent has "python-requests/2.34.2"
| project TimeGenerated, ServicePrincipalId, ServicePrincipalName, IPAddress, UserAgent, ResourceDisplayName, ResultType, ResultDescription, CorrelationId
| order by TimeGenerated desc
Hunt 3 — Sustained high-volume discovery by one caller
AzureActivity
| where TimeGenerated > ago(30d)
| where ActivityStatusValue =~ "Success"
| where OperationNameValue endswith "/read" or OperationNameValue has_any ("list", "get")
| summarize Reads=count(), ResourceTypes=dcount(ResourceProviderValue), Resources=dcount(ResourceId), SampleOperations=make_set(OperationNameValue, 20), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by bin(TimeGenerated, 1h), Caller, CallerIpAddress, SubscriptionId
| where Reads >= 50 and Resources >= 10
| order by Reads desc
Hunt 4 — Bulk resource deletion bursts
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue endswith "/delete" or OperationNameValue has "delete"
| summarize DeleteAttempts=count(), SuccessfulDeletes=countif(ActivityStatusValue =~ "Success"), ResourceGroups=dcount(ResourceGroup), Resources=dcount(ResourceId), Operations=make_set(OperationNameValue, 25) by bin(TimeGenerated, 10m), Caller, CallerIpAddress, SubscriptionId
| where DeleteAttempts >= 10 or Resources >= 5
| order by DeleteAttempts desc
Hunt 5 — Storage account deletion and access-key collection
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue has_any ("Microsoft.Storage/storageAccounts/delete", "Microsoft.Storage/storageAccounts/listKeys/action", "listkeys")
| project TimeGenerated, SubscriptionId, Caller, CallerIpAddress, OperationNameValue, ActivityStatusValue, ResourceGroup, ResourceId, CorrelationId
| order by TimeGenerated desc
Hunt 6 — Recovery protection or resource-lock deletion
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue has "delete"
| where OperationNameValue has_any ("Microsoft.Authorization/locks", "Microsoft.RecoveryServices", "backup", "siteRecovery") or ResourceId has_any ("/locks/", "/Microsoft.RecoveryServices/")
| project TimeGenerated, SubscriptionId, Caller, CallerIpAddress, OperationNameValue, ActivityStatusValue, ResourceGroup, ResourceId, Properties
| order by TimeGenerated desc
Hunt 7 — App Service configuration enumeration by workload identities
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue has_any ("Microsoft.Web/sites/config/list", "Microsoft.Web/sites/config/read", "Microsoft.Web/sites/host/listkeys/action")
| project TimeGenerated, SubscriptionId, Caller, CallerIpAddress, OperationNameValue, ActivityStatusValue, ResourceGroup, ResourceId, CorrelationId
| order by TimeGenerated desc
Hunt 8 — Discovery followed by destructive activity from the same caller
let lookback = 30d;
let Discovery = AzureActivity
| where TimeGenerated > ago(lookback) and ActivityStatusValue =~ "Success"
| where OperationNameValue endswith "/read" or OperationNameValue has_any ("list", "get")
| summarize DiscoveryStart=min(TimeGenerated), DiscoveryEnd=max(TimeGenerated), Reads=count(), Resources=dcount(ResourceId) by Caller, SubscriptionId;
let Destruction = AzureActivity
| where TimeGenerated > ago(lookback)
| where OperationNameValue has "delete"
| summarize DestructionStart=min(TimeGenerated), Deletes=count(), DeleteOps=make_set(OperationNameValue, 20) by Caller, SubscriptionId;
Discovery
| join kind=inner Destruction on Caller, SubscriptionId
| where DestructionStart between (DiscoveryStart .. DiscoveryEnd + 24h)
| where Reads >= 50 and Deletes >= 5
| project Caller, SubscriptionId, DiscoveryStart, DiscoveryEnd, Reads, Resources, DestructionStart, Deletes, DeleteOps
| order by DestructionStart desc
Hunt 9 — Rare service-principal source IP and user-agent combinations
let baseline = AADServicePrincipalSignInLogs
| where TimeGenerated between (ago(30d) .. ago(1d)) and tostring(ResultType) == "0"
| summarize Baseline=count() by ServicePrincipalId, IPAddress, UserAgent;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(1d) and tostring(ResultType) == "0"
| summarize Recent=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Resources=make_set(ResourceDisplayName, 20) by ServicePrincipalId, ServicePrincipalName, IPAddress, UserAgent
| join kind=leftanti baseline on ServicePrincipalId, IPAddress, UserAgent
| order by Recent desc
Detection Notes
- Hunts 4–6 are highest signal because mass deletions, key retrieval, and recovery-control deletion are rare and directly mirror observed activity.
Callermay be an application ID, object ID, UPN, or claim-derived value depending on Azure Activity export. Resolve it against Entra inventory before acting.- The thresholds reflect campaign scale but must be reduced for small tenants and automation-heavy identities. Approved infrastructure-as-code teardown can closely resemble destruction.
AADServicePrincipalSignInLogsrequires Entra diagnostic export and sufficient licensing. Token acquisition may precede retention, while control-plane use remains visible inAzureActivity.- The published IPs are high-confidence but not exhaustive and may be reused or changed. User-agent strings are spoofable.
- Data-plane deletions may require Storage, SQL, Key Vault, or Defender telemetry;
AzureActivityprimarily covers ARM control-plane operations.