Abstract digital art with vibrant purple and pink gradient texture on a black background.

Detailed Report: Storm-2372 DDevice Code Phishing Campaign

Detailed Report: Storm-2372 DDevice Code Phishing Campaign

Detailed Report: Storm-2372 DDevice Code Phishing Campaign

Storm-2372 is a sophisticated nation-state threat actor, with moderate to high confidence of Russian origin, that has been conducting an advanced device code phishing campaign against Microsoft 365 environments since August 2024. This threat actor exploits legitimate Microsoft OAuth 2.0 authentication flows to steal user credentials and bypass multi-factor authentication (MFA), gaining persistent access to organizational resources across government, NGO, IT services, defense, telecommunications, healthcare, education, and energy sectors in Europe, North America, Africa, and the Middle East.

1. Threat Actor Profile

Attribution

  • Name: Storm-2372 (also known as DEV-0586)

  • Confidence Level: Moderate to High for Russian state-aligned interests

  • First Observed: August 2024

  • Last Major Update: February 14, 2025 (ongoing campaign with evolving tactics)

Motivation

State-sponsored cyber operations targeting government and critical infrastructure organizations. The actor demonstrates advanced capabilities focused on long-term access to sensitive organizational data.

2. Attack Overview

Primary Objective

Storm-2372 exploits the device code authentication flow to capture valid Microsoft account access tokens, enabling:

  • Persistent access to user accounts even with MFA enabled

  • Lateral movement within organizations

  • Data exfiltration via Microsoft Graph API

  • Compromise of email, cloud storage, and other services

Attack Methodology

The campaign leverages a high-trust, low-detection approach combining social engineering with legitimate authentication mechanisms:

  1. Initial Contact: Threat actors establish rapport through third-party messaging platforms (WhatsApp, Signal) or Microsoft Teams, posing as trusted individuals (colleagues, officials, executives)

  2. Phishing Lure: Victims receive invitations to "Microsoft Teams meetings" or requests to review documents via email

  3. Code Request: Clicking the invitation redirects users to a legitimate Microsoft device code authentication page (microsoft.com/devicelogin)

  4. Token Theft: Users enter a numeric/alphanumeric code prompted by the attack, granting attackers a valid access token without triggering MFA warnings

  5. Post-Compromise: Attackers use stolen tokens to:

    • Access emails and cloud data

    • Send internal phishing messages from compromised accounts

    • Harvest additional credentials via keyword searches (passwords, admin tools, secrets)

3. Technical Details

Exploited Authentication Flow

OAuth 2.0 Device Authorization Grant: A legitimate Microsoft security feature designed for devices with limited input capabilities (e.g., smartwatches, IoT devices). Attackers abuse this by tricking users into entering codes they did not initiate themselves.

Attack Indicators

Phishing Lure Characteristics

  • Email Subject: Professional phrasing referencing urgent meetings or document reviews

  • Sender Address: Often from Microsoft domain (microsoft.com) to appear legitimate

  • Urgency Tactics: Claims of "urgent" meetings requiring immediate authentication

Device Code Authentication Page Features

  • Legitimate Microsoft URL structure: https://login.microsoftonline.com/common/oauth2/devicecode/{client-id} or microsoft.com/devicelogin

  • Clean, official Microsoft login interface with no visual indicators of malicious intent

  • No "report suspicious" or security warning banners

Code Format

The phishing code is alphanumeric and numeric-only, displayed on a legitimate authentication prompt.

4. Evolution of Tactics (February 2025 Update)

New Capabilities Discovered (Microsoft Security Blog, February 14, 2025)

Microsoft Authentication Broker Exploitation:
Since mid-February 2025, Storm-2372 has advanced their technique by:

  1. Using the specific client ID for Microsoft Authentication Broker in device code flows

  2. Receiving refresh tokens that can request additional tokens for device registration services

  3. Registering actor-controlled devices within Entra ID (formerly Azure AD)

  4. Obtaining a Primary Refresh Token (PRT) with full organizational resource access

Lateral Movement Enhancement

Compromised accounts are used to send internal phishing emails ("Document to review" invitations) to colleagues, extending attack reach across organizational networks.

Data Exfiltration Methods

  • Microsoft Graph API searches for keywords: "password," "secret," "confidential," "ministry," "gov," "teamviewer," "anydesk"

  • Email content harvesting from search results

  • Cloud storage access via stolen tokens

5. Target Sectors and Regions

Primary Industries Targeted

  • Government (federal, state/provincial, local agencies)

  • Non-Governmental Organizations (NGOs)

  • IT Services and Technology Companies

  • Defense and Military Organizations

  • Telecommunications Providers

  • Healthcare and Hospitals

  • Higher Education Institutions

  • Energy/Oil & Gas Sector

Geographic Reach

  • Europe: Multiple countries with significant government and enterprise targets

  • North America: United States, Canada, Mexico

  • Africa: South Africa, Egypt, Nigeria, Kenya

  • Middle East: Saudi Arabia, UAE, Israel, Jordan

6. MITRE ATT&CK Framework Mapping

Technique

ID

Description

Storm-2372 Usage

Spearphishing Attachment/Link

T1566.001/T1566.002

Phishing emails with malicious content

Initial access via Teams meeting invitations

Valid Accounts

T1078

Using legitimate credentials to access resources

Stolen device code tokens enable account access

Application Layer Protocol: Web Protocols

T1071.001

Web-based protocols for data exfiltration

HTTP/HTTPS for authentication flows

Mail Protocols

T1071.003

Email protocols for lateral movement

Phishing emails from compromised accounts

DNS Protocols

T1071.004

DNS queries for infrastructure support

Domain resolution for Microsoft services

Modify Authentication Process: Device Code Phishing

T1556.004

Exploiting device code authentication

Primary attack vector

7. Indicators of Compromise (IoCs)

Network Indicators

  • URLs to Monitor:

    • https://login.microsoftonline.com/common/oauth2/devicecode/*

    • https://microsoft.com/devicelogin/*

  • Client IDs: Specific Microsoft Authentication Broker client IDs (February 2025+)

  • Proxies: Regionally-appropriate proxies used to mask attack origins

Account Indicators

  • Unusual device code authentication requests from unrecognized locations

  • Login attempts without user-initiated authentication

  • Multiple failed sign-in attempts following successful compromise

  • Unexpected access to email, OneDrive, SharePoint from unusual devices

Behavioral Indicators

  • Email Exfiltration: Microsoft Graph API searches for sensitive keywords

  • Lateral Movement: Internal phishing emails sent from compromised accounts

  • Device Registration: New device enrollments in Entra ID (February 2025+)

8. Detection and Monitoring

Recommended Hunting Queries (Microsoft Sentinel/Defender)

Device Code Phishing Detection Query

let suspiciousUserClicks = materialize(UrlClickEvents 
    | where ActionType in ("ClickAllowed", "UrlScanInProgress", "UrlErrorPage") 
    or IsClickedThrough != "0" 
    | where UrlChain has_any ("microsoft.com/devicelogin", "login.microsoftonline.com/common/oauth2/deviceauth") 
    | extend AccountUpn = tolower(AccountUpn) 
    | project ClickTime = Timestamp, ActionType, UrlChain, NetworkMessageId, Url, AccountUpn);

let interestedUsersUpn = suspiciousUserClicks 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let suspiciousSignIns = materialize(AADSignInEventsBeta 
    | where ErrorCode == 0 
    | where AccountUpn in~ (interestedUsersUpn) 
    | where RiskLevelDuringSignIn in (10, 50, 100) 
    | extend AccountUpn = tolower(AccountUpn) 
    | join kind=inner suspiciousUserClicks on AccountUpn 
    | where (Timestamp - ClickTime) between (-2min .. 7min) 
    | project Timestamp, ReportId, ClickTime, AccountUpn, RiskLevelDuringSignIn, SessionId, IPAddress, Url);

let interestedSessionUsers = suspiciousSignIns 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let shortIntervalSignInAttemptUsers = materialize(AADSignInEventsBeta 
    | where AccountUpn in~ (interestedSessionUsers) 
    | where ErrorCode in (0, 50199) 
    | summarize ErrorCodes = make_set(ErrorCode) by AccountUpn, CorrelationId, SessionId 
    | where ErrorCodes has_all (0, 50199) 
    | distinct AccountUpn);

suspiciousSignIns 
| where AccountUpn in (shortIntervalSignInAttemptUsers)
let suspiciousUserClicks = materialize(UrlClickEvents 
    | where ActionType in ("ClickAllowed", "UrlScanInProgress", "UrlErrorPage") 
    or IsClickedThrough != "0" 
    | where UrlChain has_any ("microsoft.com/devicelogin", "login.microsoftonline.com/common/oauth2/deviceauth") 
    | extend AccountUpn = tolower(AccountUpn) 
    | project ClickTime = Timestamp, ActionType, UrlChain, NetworkMessageId, Url, AccountUpn);

let interestedUsersUpn = suspiciousUserClicks 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let suspiciousSignIns = materialize(AADSignInEventsBeta 
    | where ErrorCode == 0 
    | where AccountUpn in~ (interestedUsersUpn) 
    | where RiskLevelDuringSignIn in (10, 50, 100) 
    | extend AccountUpn = tolower(AccountUpn) 
    | join kind=inner suspiciousUserClicks on AccountUpn 
    | where (Timestamp - ClickTime) between (-2min .. 7min) 
    | project Timestamp, ReportId, ClickTime, AccountUpn, RiskLevelDuringSignIn, SessionId, IPAddress, Url);

let interestedSessionUsers = suspiciousSignIns 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let shortIntervalSignInAttemptUsers = materialize(AADSignInEventsBeta 
    | where AccountUpn in~ (interestedSessionUsers) 
    | where ErrorCode in (0, 50199) 
    | summarize ErrorCodes = make_set(ErrorCode) by AccountUpn, CorrelationId, SessionId 
    | where ErrorCodes has_all (0, 50199) 
    | distinct AccountUpn);

suspiciousSignIns 
| where AccountUpn in (shortIntervalSignInAttemptUsers)
let suspiciousUserClicks = materialize(UrlClickEvents 
    | where ActionType in ("ClickAllowed", "UrlScanInProgress", "UrlErrorPage") 
    or IsClickedThrough != "0" 
    | where UrlChain has_any ("microsoft.com/devicelogin", "login.microsoftonline.com/common/oauth2/deviceauth") 
    | extend AccountUpn = tolower(AccountUpn) 
    | project ClickTime = Timestamp, ActionType, UrlChain, NetworkMessageId, Url, AccountUpn);

let interestedUsersUpn = suspiciousUserClicks 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let suspiciousSignIns = materialize(AADSignInEventsBeta 
    | where ErrorCode == 0 
    | where AccountUpn in~ (interestedUsersUpn) 
    | where RiskLevelDuringSignIn in (10, 50, 100) 
    | extend AccountUpn = tolower(AccountUpn) 
    | join kind=inner suspiciousUserClicks on AccountUpn 
    | where (Timestamp - ClickTime) between (-2min .. 7min) 
    | project Timestamp, ReportId, ClickTime, AccountUpn, RiskLevelDuringSignIn, SessionId, IPAddress, Url);

let interestedSessionUsers = suspiciousSignIns 
    | where isnotempty(AccountUpn) 
    | distinct AccountUpn;

let shortIntervalSignInAttemptUsers = materialize(AADSignInEventsBeta 
    | where AccountUpn in~ (interestedSessionUsers) 
    | where ErrorCode in (0, 50199) 
    | summarize ErrorCodes = make_set(ErrorCode) by AccountUpn, CorrelationId, SessionId 
    | where ErrorCodes has_all (0, 50199) 
    | distinct AccountUpn);

suspiciousSignIns 
| where AccountUpn in (shortIntervalSignInAttemptUsers)

Newly Registered Devices Query

CloudAppEvents 
| where AccountDisplayName == "Device Registration Service" 
| extend ApplicationId_ = tostring(ActivityObjects[0].ApplicationId) 
| extend ServiceName_ = tostring(ActivityObjects[0].Name) 
| extend DeviceName = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[1].NewValue) 
| extend DeviceId = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ModifiedProperties))[6].NewValue))[0]) 
| extend DeviceObjectId_ = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[0].NewValue) 
| extend UserPrincipalName = tostring(RawEventData.ObjectId) 
| project TimeGenerated, ServiceName_, DeviceName, DeviceId, DeviceObjectId_, UserPrincipalName
CloudAppEvents 
| where AccountDisplayName == "Device Registration Service" 
| extend ApplicationId_ = tostring(ActivityObjects[0].ApplicationId) 
| extend ServiceName_ = tostring(ActivityObjects[0].Name) 
| extend DeviceName = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[1].NewValue) 
| extend DeviceId = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ModifiedProperties))[6].NewValue))[0]) 
| extend DeviceObjectId_ = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[0].NewValue) 
| extend UserPrincipalName = tostring(RawEventData.ObjectId) 
| project TimeGenerated, ServiceName_, DeviceName, DeviceId, DeviceObjectId_, UserPrincipalName
CloudAppEvents 
| where AccountDisplayName == "Device Registration Service" 
| extend ApplicationId_ = tostring(ActivityObjects[0].ApplicationId) 
| extend ServiceName_ = tostring(ActivityObjects[0].Name) 
| extend DeviceName = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[1].NewValue) 
| extend DeviceId = tostring(parse_json(tostring(parse_json(tostring(RawEventData.ModifiedProperties))[6].NewValue))[0]) 
| extend DeviceObjectId_ = tostring(parse_json(tostring(RawEventData.ModifiedProperties))[0].NewValue) 
| extend UserPrincipalName = tostring(RawEventData.ObjectId) 
| project TimeGenerated, ServiceName_, DeviceName, DeviceId, DeviceObjectId_, UserPrincipalName

9. Mitigation and Defense Strategies

Immediate Actions (Microsoft Security Recommendations)

  1. Block Device Code Flow Where Possible

    • Configure Microsoft Entra ID's device code flow in Conditional Access policies

    • Block device code authentication wherever not strictly required

  2. Revoke Tokens Upon Compromise Detection

    • Call revokeSignInSessions API endpoint immediately if compromise suspected

    • Force re-authentication via Conditional Access policy for affected users

  3. Implement Sign-In Risk Policies

    • Automate response to risky sign-ins based on risk level

    • Block access or force MFA for high-risk sign-ins

    • Monitor risky sign-in reports in Azure Portal

  4. Deploy Phishing-Resistant MFA

    • Use FIDO2 security keys

    • Implement Microsoft Authenticator with certificate-based authentication

    • Avoid telephony-based MFA (SIM-jacking vulnerability)

  5. Block Legacy Authentication

    • Use Conditional Access to block legacy auth protocols

    • Legacy MFA is susceptible to abuse and device code phishing

Long-Term Hardening Measures

  1. Centralized Identity Management

    • Consolidate identity platforms

    • Implement Single Sign-On (SSO)

    • Synchronize all user accounts (except privileged ones) between on-premises and cloud

  2. Credential Hygiene Best Practices

    • Apply principle of least privilege

    • Audit privileged account activity

    • Regular password rotation for sensitive roles

  3. User Education and Awareness

    • Train users to never enter device codes they did not initiate

    • Emphasize importance of recognizing legitimate authentication prompts

    • Educate about social engineering indicators (urgency, authority, secrecy)

  4. Email Security Enhancements

    • Deploy advanced email filtering for Teams meeting invitations

    • Analyze sender reputation and domain validation

    • Implement DMARC, DKIM, and SPF policies

10. References and Further Reading

Primary Sources

Additional Resources

  • eSentire Advisory: Device Code Authentication Phishing security advisories

  • SOCRadar Research: Storm-2372 Russian APT analysis

  • Volexity: Multiple reports on device code authentication attacks

  • Black Hills Information Security: Dynamic device code phishing research

  • Huntress Labs: OAuth 2.0 device code phishing in Google Cloud and Azure

11. Conclusion

Storm-2372 represents a sophisticated, evolving threat to Microsoft 365 environments worldwide. The campaign's effectiveness stems from exploiting legitimate authentication mechanisms rather than traditional vulnerabilities, making detection challenging without specialized monitoring.

Key Takeaways for Organizations

  1. Device code phishing is active and ongoing as of February 2025 with evolving techniques

  2. Russian state-sponsored actor with access to advanced capabilities

  3. Successful attacks against government and critical infrastructure sectors

  4. Lateral movement capability extends beyond initial compromise

  5. February 2025 evolution introduces new persistent access capabilities

Immediate Actions Required

  • Review and restrict device code authentication flow usage

  • Implement phishing-resistant MFA (FIDO2, certificate-based)

  • Deploy detection queries for anomalous device registrations

  • Conduct user awareness training on device code phishing indicators

  • Monitor for unauthorized token refresh and device enrollments in Entra ID

Long-Term Recommendations

  • Centralize identity management across cloud environments

  • Implement continuous monitoring of authentication logs

  • Establish threat hunting program focusing on OAuth-based attacks

  • Maintain updated MITRE ATT&CK mappings for attack detection

Assessment: This campaign poses critical risk to organizations relying heavily on Microsoft 365, particularly government entities and critical infrastructure. Immediate remediation of device code flow usage and implementation of phishing-resistant authentication are essential defensive priorities.

Let Your CISO SSleep at Night.

Let Your CISO SSleep at Night.

Let Your CISO SSleep at Night.

Understand how ATLAS Cyber offers word class detection and response with 0 false positives.