Storm-2949 has redefined the scope of cloud intrusions, proving that a single compromised identity can serve as a master key for an entire environment without deploying a single line of malicious code. According to the Microsoft Security Blog, this threat actor successfully transitioned from initial credential access to large-scale data exfiltration by abusing trusted native cloud mechanisms.
For defenders, this is a critical wake-up call. Traditional preventative controls—EDR, antivirus, and signature-based detection—are effectively blind to this threat. Storm-2949 operates entirely within the "authorized" scope of the compromised identity's permissions. If we rely solely on detecting "bad" binaries, we will fail. We must shift our focus to detecting "bad" behavior from "trusted" accounts.
Technical Analysis
Affected Platforms: Microsoft 365, Azure Active Directory (Entra ID), Azure Storage.
Attack Vector: Credential Theft & Living Off the Land (LotL).
Attack Chain Breakdown:
- Initial Access: Acquisition of valid credentials (likely via phishing or token theft).
- Discovery & Persistence: The actor utilizes native cloud APIs (Microsoft Graph, Azure REST Management APIs) to enumerate directory roles, audit logs, and sensitive resources. They often create new Service Principals or modify existing application permissions to maintain persistence.
- Data Exfiltration: Unlike traditional ransomware that encrypts data, Storm-2949 utilizes standard administrative tools to stage and export data. This includes heavy usage of
Exchange OnlinePowerShell cmdlets or direct Graph API calls to download mailboxes and SharePoint files. - Defense Evasion: By using built-in administrative interfaces and the
Azure PortalorPowerShellmodules, all activity appears as standard administrative traffic, blending in with legitimate operations.
Exploitation Status: Confirmed active exploitation (In-the-wild). No CVE required; the vulnerability is weak identity governance (MFA gaps, excessive privileges).
Detection & Response
The following detection logic focuses on identifying the abuse of privileged access and anomalous data movement patterns characteristic of Storm-2949. These queries prioritize high-fidelity indicators of data staging and privilege escalation.
Sigma Rules
---
title: Storm-2949 Azure Role Assignment via PowerShell
id: 9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects the addition of role assignments or App Role assignments using Azure AD PowerShell, a tactic used by Storm-2949 for persistence.
references:
- https://attack.mitre.org/techniques/T1098/
author: Security Arsenal
date: 2026/05/19
tags:
- attack.persistence
- attack.t1098.003
logsource:
product: azure
service: auditlogs
detection:
selection:
LoggedByService: 'Core Directory'
Category: 'ApplicationManagement'
OperationName|contains:
- 'Add member to role'
- 'Add app role assignment to service principal'
condition: selection
falsepositives:
- Legitimate administrative delegation
level: high
---
title: Potential Data Exfiltration via Office Management API
id: 1b2c3d4e-5f6a-7b8c-9d0e-1f2a3b4c5d6e
status: experimental
description: Detects high volume of file export or download operations in SharePoint/OneDrive consistent with bulk data theft.
references:
- https://attack.mitre.org/techniques/T1567/
author: Security Arsenal
date: 2026/05/19
tags:
- attack.exfiltration
- attack.t1567.001
logsource:
product: o365
service: sharepoint
detection:
selection:
Operation|contains:
- 'FileDownloaded'
- 'FileCheckedOut'
Workload: 'SharePoint'
filter:
UserId|contains:
- 'bot@'
- 'service@'
condition: selection and not filter
falsepositives:
- Bulk user migrations or authorized backups
level: medium
KQL (Microsoft Sentinel / Defender)
// Hunt for Storm-2949: Anomalous Exchange Online PowerShell module usage
// Look for MailItemsAccessed events in bulk, which often indicates data staging
ExchangeItemOperations
| where Operation in ("MailItemsAccessed", "UpdateInboxRules")
| extend ClientAppString = tostring(Folders[0].ClientAppString)
| where ClientAppString == "ExchangePowerShell"
| summarize count() by Operation, UserId, ClientIPAddress, bin(TimeGenerated, 5m)
| where count_ > 50 // Threshold tuning required based on environment
| order by count_ desc
| extend Timestamp = TimeGenerated
| project Timestamp, UserId, ClientIPAddress, Operation, AccessCount = count_
// Hunt for Storm-2949: Privileged Identity Management (PIM) Role Activation Anomalies
// Detects elevation of roles from unusual IPs or impossible travel scenarios
AuditLogs
| where Category == "RoleManagement"
| where OperationName in ("Add member to role", "Activate Eligible Role")
| extend TargetAppName = tostring(TargetResources[0].AppDisplayName)
| extend InitiatorUser = tostring(InitiatedBy.User.UserPrincipalName)
| extend InitiatorIP = iff(InitiatedBy.User.IPAddress == "", tostring(InitiatedBy.App.IPAddress), tostring(InitiatedBy.User.IPAddress))
| project TimeGenerated, OperationName, InitiatorUser, InitiatorIP, TargetAppName, Properties
| sort by TimeGenerated desc
Velociraptor VQL
-- Hunt for Azure/Exchange PowerShell processes on workstations
-- Identifies endpoints where admins may be running tools to dump data
SELECT Pid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE Name =~ 'powershell.exe'
AND (CommandLine =~ 'Connect-ExchangeOnline'
OR CommandLine =~ 'Connect-AzureAD'
OR CommandLine =~ 'Connect-MgGraph'
OR CommandLine =~ 'Export-Mailbox')
AND Exe NOT LIKE '%Program Files%\\%'
AND Exe NOT LIKE '%System32%\\WindowsPowerShell\\v1.0%'
Remediation Script (PowerShell)
# Incident Response Script: Audit High-Risk App Registrations and Service Principals
# Use this to identify backdoors created by Storm-2949
Write-Host "[+] Checking for App Registrations with high-privilege permissions..." -ForegroundColor Cyan
Connect-AzureAD -WarningAction SilentlyContinue | Out-Null
$HighRiskRoles = @(
"Application.ReadWrite.All",
"Directory.ReadWrite.All",
"RoleManagement.ReadWrite.Directory",
"Mail.ReadWrite"
)
$Apps = Get-AzureADApplication -All $true
$RiskFindings = @()
foreach ($App in $Apps) {
$SP = Get-AzureADServicePrincipal -ObjectId $App.ObjectId
$Oauth2Perms = $SP.Oauth2Permissions | Where-Object { $_.Value -in $HighRiskRoles -and $_.Type -eq "Admin" }
$AppRoles = $SP.AppRoles | Where-Object { $_.Value -in $HighRiskRoles }
if ($Oauth2Perms -or $AppRoles) {
$Details = [PSCustomObject]@{
AppName = $App.DisplayName
AppId = $App.AppId
ObjectId = $App.ObjectId
RiskType = "High Privilege"
Permissions = ($Oauth2Perms.Value + $AppRoles.Value) -join ", "
}
$RiskFindings += $Details
}
}
if ($RiskFindings.Count -gt 0) {
Write-Host "[!] WARNING: Found high-risk application permissions." -ForegroundColor Red
$RiskFindings | Format-Table -AutoSize
} else {
Write-Host "[+] No high-risk application permissions detected." -ForegroundColor Green
}
Write-Host "[+] Checking for recently created Service Principals (Last 7 Days)..." -ForegroundColor Cyan
$CutoffDate = (Get-Date).AddDays(-7)
$RecentSPs = Get-AzureADServicePrincipal -All $true | Where-Object { $_.CreationDate -gt $CutoffDate }
if ($RecentSPs) {
Write-Host "[!] WARNING: Recent Service Principals found." -ForegroundColor Yellow
$RecentSPs | Select-Object DisplayName, AppId, CreationDate | Format-Table -AutoSize
} else {
Write-Host "[+] No recent Service Principal creation detected." -ForegroundColor Green
}
Remediation
Based on the Storm-2949 TTPs, immediate containment requires revoking the trust the actor currently holds. Follow these steps in order:
- Reset Credentials & Invalidate Tokens: Immediately reset passwords for all affected accounts. Force a sign-out of all sessions using the
Revoke-AzureADUserAllRefreshTokencmdlet or via the Microsoft 365 Admin Center to kill active refresh tokens. - Review and Revoke App Permissions: Audit all OAuth2 grants (
ServicePrincipals). Revoke permissions for any application that is not explicitly recognized or that was created around the time of the breach. specifically look forApplication.ReadWrite.AllorDirectory.ReadWrite.All. - Enable Conditional Access (CA):
- Block legacy authentication protocols.
- Enforce MFA for all admin roles.
- Require device compliance (Hybrid Azure AD Join) for sensitive access.
- Configure Privileged Identity Management (PIM): Ensure standing admin access is removed. Administrators must "activate" their roles only when needed, providing an audit trail.
- Official Vendor Guidance: Refer to the Microsoft Storm-2949 Advisory for specific IOCs related to your tenant.
Related Resources
Security Arsenal Incident Response Services AlertMonitor Platform Book a SOC Assessment incident-response Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.