Storm-2949 - SSPR Reset + Fake IT Call → MFA Method Takeover → Azure RBAC Abuse → Key Vault, SQL and Storage Theft
On May 18, 2026 Microsoft Threat Intelligence published its analysis of Storm-2949, a threat activity cluster that broke into Microsoft 365 and Azure tenants with no malware at initial access. Microsoft assesses with high confidence that the actor started a Microsoft Entra ID Self-Service Password Reset (SSPR) on a target's behalf and then phoned that target while impersonating internal IT support, so the victim would approve the resulting MFA prompt as part of what sounded like a routine reset. With the account in hand the actor removed the victim's existing authentication methods, enrolled its own Microsoft Authenticator device, and then rode existing privileged Azure RBAC assignments across multiple subscriptions into App Service publishing profiles, Key Vault secrets, SQL databases, storage accounts, and virtual machines. Victim organizations were not named; the targeted users were IT personnel and senior leadership, and the objective was to exfiltrate as much data as possible from high-value assets.
Microsoft is explicit about its confidence here: it assesses with high confidence that Storm-2949 used a social engineering technique consistent with known abuses of the SSPR process, an assessment drawn from the roles of the compromised users and the investigation findings rather than from a directly observed initial-access artifact. The actor triggered the SSPR flow for a chosen user, then contacted that user by phone while impersonating an internal IT support representative, claiming the account needed urgent verification and instructing them to approve the MFA prompt as part of a routine password reset. The prompt was genuine, so number matching and push approval bought nothing: the control was validating a real Entra ID request, not the identity of the person who asked for it. Victim selection skewed to IT personnel and senior leadership, which Microsoft says indicated deliberate targeting and, in its assessment, an organized and convincing phishing scheme.
Confidence: Microsoft assesses this vector with high confidence rather than asserting direct observation
Targets: IT personnel and senior leadership - users likely to hold privileged roles
Why it worked: The MFA prompt was legitimate; a user primed to expect it will approve it, and number matching cannot tell the difference
Defense gap: SSPR accepted push and number-match approval instead of phishing-resistant verification
Holding the reset flow, the actor rewrote the account's authentication posture rather than simply using it. Registered phone numbers, email addresses, and prior Microsoft Authenticator registrations were removed, and an attacker-controlled Authenticator device was enrolled in their place. That single change turned MFA from a control into an asset: every subsequent sign-in satisfied MFA and Conditional Access policy using the attacker's device, while the real owner lost the exact methods they would have needed to recover the account. The actor also tried to push persistence past user accounts entirely by adding credentials to a compromised Entra service principal, which would have survived any user-side password reset. That attempt failed for lack of permissions.
Method added: Attacker-controlled Microsoft Authenticator device
Attempted and failed: Adding credentials to a compromised Entra service principal, blocked by insufficient permissions
Effect: MFA and Conditional Access now attest the attacker's device; the legitimate user cannot self-recover
Detection signal: Microsoft Defender XDR alert for suspicious addition of a default third-party MFA method to a user account
Instead of clicking through the portal, the actor issued automated API requests through Microsoft Graph using custom Python scripts, enumerating users and applications within the tenant. It searched Entra ID for accounts by name pattern and role attribute, which Microsoft assesses was likely intended to identify privileged identities and additional high-value targets, and it mapped the applications and service principals registered in the tenant. Graph reads of this kind are authenticated, expected, and high volume in most tenants, so scripted enumeration coming from a legitimate compromised account produces very little that stands out without behavior baselining on request volume and pattern.
Enumerated: User accounts by name pattern and role attribute, groups, applications, service principals
Purpose: Locate privileged identities and follow-on targets inside the tenant
Detection gap: Graph reads from a valid user session resemble ordinary tooling unless volume and pattern are baselined
microsoft.Web/sites/publishxml/action handed over App Service deployment credentials and Kudu console accessNo exploit was required at this stage. The compromised identities already carried privileged custom Azure RBAC roles on several subscriptions, so the actor simply used the authorization the accounts were already holding. It called microsoft.Web/sites/publishxml/action to retrieve App Service publishing profiles, which carry FTP and Web Deploy deployment credentials inside the profile itself, and used those to reach the Kudu console. Kudu is the built-in administrative interface for Azure App Services that lets an authenticated user browse the file system, inspect environment variables, and execute commands in the application's own context. An ARM permission that reads like a deployment convenience therefore delivered interactive command execution inside production web applications.
microsoft.Web/sites/publishxml/action - retrieves the App Service publishing profileCredentials obtained: FTP and Web Deploy deployment credentials, carried in the profile
Follow-on access: Kudu console - file system browsing, environment variables, command execution in the app context
Why it worked: Privileged custom RBAC roles were already assigned to the compromised users across multiple subscriptions
Defense gap:
publishxml is classed as a management action, but it functions as credential issuance
The actor manipulated Key Vault access configurations and then read dozens of secrets out of the vault, all inside roughly four minutes. Those secrets included database connection strings and identity credentials, which Microsoft notes dramatically expanded the attack's blast radius. The structural lesson for defenders sits in the sequence: changing who may read a vault is an ARM control-plane operation, while reading the secrets is a data-plane operation. An actor with sufficient control-plane rights can grant itself data-plane access and spend it long before a human reviews the policy change.
Volume and window: Dozens of secrets in roughly four minutes
Secret types: Database connection strings, identity credentials, and more
Why it worked: Control-plane rights over the vault were convertible into data-plane read access on demand
Defense gap: No alert on the access-configuration change, and no rate anomaly raised on bulk secret reads
firewallrules/write opened Azure SQL to attacker IPs and storageAccounts/listkeys/action produced account keys and SAS tokensThis is the hinge of the entire intrusion. Holding connection strings from Key Vault, the actor still needed a network path, so it called microsoft.sql/servers/firewallrules/write to add its own addresses to the Azure SQL server firewall and then signed in to the databases with the harvested credentials. In parallel it used microsoft.storage/storageaccounts/write to manipulate storage account network access configuration and microsoft.Storage/storageAccounts/listkeys/action to obtain account keys and shared access signature (SAS) tokens. Not one of those operations reads a byte of customer data by itself, and every one of them looks like ordinary infrastructure management on a change ticket. Chained, they are a complete path from Azure Resource Manager rights to live production data.
microsoft.sql/servers/firewallrules/write, microsoft.storage/storageaccounts/write, microsoft.Storage/storageAccounts/listkeys/actionResult: SQL server firewall opened to attacker addresses; storage account keys and SAS tokens obtained
Credentials used: Database connection strings taken from Key Vault in the previous step
Attacker IPs: 176.123.4[.]44, 91.208.197[.]87
Why it worked:
listkeys and firewallrules/write are classed as management permissions but behave as direct data access
From the Azure control plane the actor reached into the guest operating system. It deployed the VMAccess extension to create backdoor local administrator accounts on targeted VMs and used Run Command to execute scripts, including an attempt to abuse a VM managed identity by requesting an access token from the Azure Instance Metadata Service and using it to authenticate to and retrieve secrets from the production web app's Key Vault. It then set about blinding the host. PowerShell scripts attempted to disable Microsoft Defender Antivirus real-time protection and behavior monitoring and to interfere with the associated service, and ScreenConnect was installed from attacker infrastructure with network requests spoofed to resemble trusted software updates, files placed in locations resembling legitimate system content, and the service renamed to resemble a Windows component. Windows event logs were cleared, and command history and temporary files were deleted.
Managed identity attempt: Request an access token from IMDS, then authenticate to the web app Key Vault with it
Defender tampering: PowerShell disabling real-time protection and behavior monitoring, plus interference with the service
RMM masquerade: ScreenConnect served from 185.241.208[.]243, service renamed to resemble a Windows component
Anti-forensics: Windows event logs cleared, command history and temporary files deleted (T1685.005)
Exfiltration ran on two tracks. In Microsoft 365 the actor used the OneDrive web interface to download thousands of files in a single action to its own infrastructure, concentrating on IT documents covering virtual private network (VPN) configuration and remote access procedures - precisely the material that makes the next intrusion cheaper. In Azure it ran a custom Python script built on the Azure SDK for Storage to programmatically enumerate and download blobs directly to an attacker-controlled endpoint device. That storage-based exfiltration continued over multiple days from the initial access, with the actor alternating between secret-based and OAuth-based authentication as access conditions and controls shifted around it. The actor also discovered and exfiltrated .pfx certificate files, artifacts that may contain private keys and could be valuable for follow-on access if imported or reused elsewhere.
Documents targeted: VPN configuration and remote access procedure documentation
Azure Storage: Custom Python script on the Azure SDK for Storage, enumerating and downloading blobs
Duration and auth: Continued over multiple days, alternating secret-based and OAuth-based authentication
Also taken: .pfx certificate files that may contain private keys usable for follow-on access
🛡 How to Defend Against This Chain
microsoft.Web/sites/publishxml/action, microsoft.Storage/storageAccounts/listkeys/action, microsoft.sql/servers/firewallrules/write, and microsoft.storage/storageaccounts/write. Every one of those reads as management and every one is, in practice, a direct path to production data. Restrict VMAccess extension deployment and Run Command the same way, and scope each VM managed identity to least privilege so a stolen IMDS token is worth as little as possible.CloudAuditEvents for Azure control-plane activity and CloudStorageAggregatedEvents for storage data-plane access. Published indicators to sweep for: 176.123.4[.]44, 91.208.197[.]87, and 185.241.208[.]243.