Part of our security guide series

security

Microsoft Device Code Flow DEBULL Abuse: 3 Fixes (2026)

Praveen10 min read
Minimal flat illustration of an OAuth device code card with an unlocked padlock and a ghost token arrow escaping
On This Page (13 sections)
Privacy Benchmark & Migration Hub

Want to stop Google from tracking your phone and browser? We ran 72-hour Wireshark packet captures and tested open-source replacements for Search, Gmail, Drive, Photos, and Android.

read our complete DeGoogle Starter Pack and benchmark data

Quick fix: Block unmanaged device code logins in Microsoft Entra ID. In Conditional Access, create a policy for Device code flow. Require a compliant or hybrid-joined PC. This stops attackers from using stolen codes on remote machines.

Three months ago, our SIEM flagged an alert in our Microsoft 365 tenant. A developer account finished 14 logins in two minutes from Frankfurt. We are based in Dubai.

We checked the sign-in logs right away. The password was valid. The user had passed MFA. No brute-force attacks showed up in our logs. Yet seconds later, an outside script began pulling emails and SharePoint files.

We were facing a live DEBULL Device Code Flow attack.

Standard phishing tricks users into typing passwords on fake websites. DEBULL is different. It uses Microsoft’s real sign-in page at microsoft.com/devicelogin. This flow was built for smart TVs and media consoles without keyboards. The attacker makes a code and tricks your user into typing it.

Below, we break down how the attack works. We share the exact Entra ID Conditional Access rules, PowerShell audit script, and Sentinel KQL queries we used to secure our tenant.


1. How the DEBULL Attack Chain Works

Attackers use the device code flow to generate real Microsoft codes. They trick victims into signing in on Microsoft’s site. Then, Microsoft sends the session token straight to the attacker’s terminal.

+-----------------------------------------------------------------------------------------+
|                    DEBULL OAUTH 2.0 DEVICE CODE EXPLOITATION PIPELINE                   |
+-----------------------------------------------------------------------------------------+

 [ Attacker Terminal ]                   [ Victim Workstation ]              [ Entra ID / M365 ]
       |                                          |                                  |
   1.  |-- POST /oauth2/v2.0/devicecode --------->|                                  |
       |   (Client: Microsoft Office / Graph CLI) |                                  |
       |<-- Returns: user_code + device_code -----|----------------------------------|
       |                                          |                                  |
   2.  |-- Phishing Email / Teams Lure ---------->|                                  |
       |   "Urgent: Enter Code [B7K-9XM-4LQ] at:  |                                  |
       |    https://microsoft.com/devicelogin"    |                                  |
       |                                          |                                  |
   3.  |                                          |-- Navigates to REAL URL -------->|
       |                                          |   microsoft.com/devicelogin      |
       |                                          |   Enters Code: [B7K-9XM-4LQ]     |
       |                                          |   Completes Corporate MFA        |
       |                                          |<-- "Authentication Successful" --|
       |                                          |                                  |
   4.  |-- Continuous Polling Loop -------------->|                                  |
       |   POST /oauth2/v2.0/token                |                                  |
       |   (grant_type=urn:ietf:params:oauth:...) |                                  |
       |<-- Issues: Access Token + Refresh Token (PRT) ------------------------------|
       |                                                                             |
   5.  |-- Graph API Exfiltration (Emails, Teams, SharePoint) ---------------------->|

Phase 1: The Attacker Starts the Request

The attacker runs an exploit script like DEBULL or TokenTactician. The tool sends a request to Microsoft’s login server:

POST /organizations/oauth2/v2.0/devicecode HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c
&scope=https://graph.microsoft.com/.default offline_access

Microsoft responds with two codes:

  • device_code: A private key kept by the attacker’s script.
  • user_code: A short 9-character code (like B7K-9XM-4LQ) shown to the user.

Phase 2: Sending the Lure

The attacker sends an urgent email or Teams message. It looks like an IT alert: “Security check needed. Go to microsoft.com/devicelogin and enter code B7K-9XM-4LQ.”

Phase 3: The Victim Signs In

The user clicks the link. The URL is 100% real. There are no fake domains or bad certificates. Email security tools trust microsoft.com and deliver the message.

The user enters the code. They approve their normal MFA prompt. Microsoft confirms the sign-in and links it to the waiting device code.

Phase 4: Token Capture

The attacker’s script checks the server every few seconds. Once the user approves the prompt, Microsoft sends the tokens back to the attacker.

The attacker receives both an Access Token and a Primary Refresh Token. The stolen token can read emails, download SharePoint files, and access team data.


2. Attack vs Defense Comparison

Standard email filters fail because the sign-in link is genuine. Only device-based access controls stop this attack.

Attack StepDefault M365 SetupHardened Zero Trust SetupSecurity Result
Phishing EmailGateways trust microsoft.com. Emails hit the inbox.Inbound filters scan body text for login links with codes.Warns security teams before users click.
Code EntryAny home PC or phone can approve the code.Conditional Access blocks logins from unmanaged devices.Blocks remote attacks. Users cannot sign in on personal devices.
MFA PromptUser approves standard MFA on their phone.Enforces phishing-resistant keys (FIDO2 or Windows Hello).Stops token replay from remote IP ranges.
Token TheftRefresh tokens stay valid for up to 90 days.Token protection binds tokens to verified device IPs.Drops tokens if the attacker IP changes.
Internal DiscoveryAttacker queries Graph API to list mailboxes.Restricts user permissions to view other directory profiles.Slows down lateral movement inside the tenant.

3. How We Blocked the Attack in Entra ID

You can stop this threat with a single Conditional Access policy. We restrict device code logins to company-managed hardware.

In the Microsoft Entra Admin Center, go to Protection → Conditional Access → New Policy:

  1. Policy Name: SEC-CA-Block-Unmanaged-DeviceCodeFlow.
  2. Users: Select All users. Exclude your emergency break-glass admin accounts.
  3. Target resources: Select All cloud apps.
  4. Conditions → Client apps: Pick Mobile apps and desktop clients and Other clients.
  5. Conditions → Authentication flows: Set Device code flow to Include.
  6. Grant: Click Grant access, then select:
    • Require device to be marked as compliant
    • Require Microsoft Entra hybrid joined device
    • Choose Require one of the selected controls.

Here is our production JSON policy export:

{
  "displayName": "SEC-CA-Block-Unmanaged-DeviceCodeFlow",
  "state": "enabled",
  "conditions": {
    "users": {
      "includeUsers": ["All"],
      "excludeUsers": ["breakglass-admin-01[at]yourdomain.com"]
    },
    "applications": {
      "includeApplications": ["All"]
    },
    "authenticationFlows": {
      "transferMethods": ["deviceCodeFlow"]
    }
  },
  "grantControls": {
    "operator": "OR",
    "builtInControls": ["compliantDevice", "domainJoinedDevice"]
  }
}

If a user opens microsoft.com/devicelogin on a personal phone, Entra ID checks the device status. Unmanaged devices lack Intune certificates. Entra ID stops the login with error AADSTS53000 (Device is not in required device state).


4. PowerShell Audit Script to Find Stolen Tokens

We wrote audit_entra_device_code.ps1 to scan our tenant. It checks the last 30 days of sign-ins and flags logins from unusual IPs.

<#
.SYNOPSIS
    Microsoft Entra ID Device Code Flow Audit & Threat Hunter (PraveenTechWorld 2026).
.DESCRIPTION
    Audits the last 30 days of sign-in logs for deviceCode authentication events,
    detects multi-IP anomalies, and verifies Conditional Access policy enforcement.
#>

[CmdletBinding()]
param(
    [int]$DaysToAudit = 30,
    [switch]$ExportCsv
)

Write-Host "============================================================" -ForegroundColor Cyan
Write-Host "  PraveenTechWorld Entra ID Device Code Security Inspector  " -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan

# Verify Microsoft.Graph module
if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Identity.SignIns)) {
    Write-Error "Microsoft.Graph.Identity.SignIns module not found. Run: Install-Module Microsoft.Graph -Scope CurrentUser"
    exit 1
}

# Connect with required audit scopes
Write-Host "`n[+] Connecting to Microsoft Graph API..." -ForegroundColor Yellow
Connect-MgGraph -Scopes "AuditLog.Read.All", "Policy.Read.All" -NoWelcome

$startDate = (Get-Date).AddDays(-$DaysToAudit).ToString("yyyy-MM-ddTHH:mm:ssZ")
Write-Host "[+] Querying Entra ID Sign-In logs since: $startDate" -ForegroundColor Yellow

# Query sign-in logs using authenticationProtocol filter
$filter = "createdDateTime ge $startDate and authenticationProtocol eq 'deviceCode'"
try {
    $deviceCodeSignins = Get-MgAuditLogSignIn -Filter $filter -All -ErrorAction Stop
} catch {
    Write-Warning "Direct filter failed; retrieving general sign-ins and filtering client-side..."
    $deviceCodeSignins = Get-MgAuditLogSignIn -Top 1000 | Where-Object { $_.AuthenticationProtocol -eq "deviceCode" }
}

Write-Host "[*] Discovered $($deviceCodeSignins.Count) Device Code sign-in events." -ForegroundColor Cyan

if ($deviceCodeSignins.Count -eq 0) {
    Write-Host "`n[OK] No Device Code authentication events recorded in the audit window." -ForegroundColor Green
    exit 0
}

# Analyze for multi-IP and anomalous token issuance
$suspiciousEvents = @()
$groupedByUser = $deviceCodeSignins | Group-Object UserPrincipalName

foreach ($group in $groupedByUser) {
    $user = $group.Name
    $uniqueIPs = $group.Group | Select-Object -ExpandProperty IpAddress -Unique
    $successes = $group.Group | Where-Object { $_.Status.ErrorCode -eq 0 }

    if ($uniqueIPs.Count -gt 1 -or $successes.Count -gt 3) {
        Write-Warning "[ALERT] Suspicious Device Code activity detected for: $user"
        Write-Warning "        Unique IPs: $($uniqueIPs -join ', ')"
        Write-Warning "        Successful Token Grants: $($successes.Count)"
        
        $suspiciousEvents += [PSCustomObject]@{
            UserPrincipalName = $user
            TotalAttempts     = $group.Count
            SuccessfulGrants  = $successes.Count
            UniqueIPCount     = $uniqueIPs.Count
            IPAddresses       = ($uniqueIPs -join '; ')
            FirstSeen         = ($group.Group | Measure-Object -Property CreatedDateTime -Minimum).Minimum
            LastSeen          = ($group.Group | Measure-Object -Property CreatedDateTime -Maximum).Maximum
        }
    }
}

Write-Host "`n============================================================" -ForegroundColor Cyan
Write-Host "  Audit Summary: $($suspiciousEvents.Count) Accounts Exhibiting High-Risk Patterns " -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Cyan

if ($suspiciousEvents.Count -gt 0) {
    $suspiciousEvents | Format-Table -AutoSize
    if ($ExportCsv) {
        $outPath = ".\Entra_DeviceCode_ThreatReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
        $suspiciousEvents | Export-Csv -Path $outPath -NoTypeInformation
        Write-Host "[+] Exported forensic threat report to: $outPath" -ForegroundColor Green
    }
}

How to Run the Tool

Run the script from an admin PowerShell window:

Set-ExecutionPolicy RemoteSigned -Scope Process -Force
.\audit_entra_device_code.ps1 -DaysToAudit 30 -ExportCsv

If you see successful logins from unknown IPs, revoke the user’s active sessions immediately. Go to Users → Select User → Revoke sessions in the admin center.


5. Detecting Attacks with Microsoft Sentinel KQL

Real-time alerts help security teams act before data leaves the tenant. We use two custom KQL rules in Microsoft Sentinel.

Add this rule under Microsoft Sentinel → Analytics → Create Scheduled Query Rule:

// KQL Query: Flag High-Risk DEBULL Device Code Phishing Patterns
// Runs every 1 hour, inspecting 2-hour lookback windows
SigninLogs
| where TimeGenerated >= ago(2h)
| where AuthenticationProtocol =~ "deviceCode"
| extend DeviceDetail = tostring(DeviceDetail.operatingSystem)
| extend Country = tostring(LocationDetails.countryOrRegion)
| summarize 
    TotalAttempts = count(),
    SuccessfulLogins = countif(ResultType == 0),
    FailedLogins = countif(ResultType != 0),
    DistinctIPs = dcount(IPAddress),
    IPList = make_set(IPAddress),
    AppList = make_set(AppDisplayName)
    by UserPrincipalName, bin(TimeGenerated, 30m)
| where SuccessfulLogins > 0 and (DistinctIPs > 1 or TotalAttempts >= 3)
| extend AccountCustomEntity = UserPrincipalName
| extend IPCustomEntity = tostring(IPList[0])
| project TimeGenerated, AccountCustomEntity, IPCustomEntity, TotalAttempts, SuccessfulLogins, DistinctIPs, IPList, AppList
| sort by TimeGenerated desc

Catching Token Replay

After stealing a token, the attacker calls the Microsoft Graph API from their own server. We catch this with a second query on AADNonInteractiveUserSignInLogs:

// KQL Query: Detect Non-Interactive Graph API Usage Post-Device Login
let deviceLogins = SigninLogs
| where TimeGenerated >= ago(24h)
| where AuthenticationProtocol =~ "deviceCode" and ResultType == 0
| project DeviceLoginTime = TimeGenerated, UserPrincipalName, OriginalIP = IPAddress;
AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(24h)
| where AppDisplayName has "Graph" or AppDisplayName has "PowerShell"
| join kind=inner (deviceLogins) on UserPrincipalName
| where TimeGenerated between (DeviceLoginTime .. (DeviceLoginTime + 2h))
| where IPAddress != OriginalIP
| project TimeGenerated, UserPrincipalName, InitialAuthIP = OriginalIP, SubsequentGraphIP = IPAddress, AppDisplayName, ResourceDisplayName

If this query returns rows, an outside server is using an internal user’s token.


6. Fixing Common Workflow Issues

Blocking device code flow can disrupt automated scripts or meeting room hardware. Here is how we resolved both cases:

  1. CLI Build Pipelines: Developers using automated scripts in Docker often relied on az login --use-device-code.
    • Fix: Switch your pipelines to Azure Managed Identities or Service Principals with Certificates. Never use personal user accounts for automated builds.
  2. Meeting Room Displays and Smart TVs: Conference panels without physical keyboards use device code logins during setup.
    • Fix: Put conference hardware into a dedicated Intune group. Enroll them on a private setup network using an admin account.

7. Security Hardening Checklist

Follow this checklist to lock down your Microsoft 365 tenant:

  • Scan Past Sign-Ins: Run our PowerShell script to review the last 30 days of logins.
  • Enforce Conditional Access: Require compliant devices for all device code sign-ins.
  • Revoke Risky Sessions: End active tokens for any user with risky logins.
  • Enable Sentinel Alerts: Deploy our KQL queries to spot multi-IP token generation.
  • Update Pipelines: Move developer scripts to Managed Identities instead of personal accounts.

For more enterprise security guides from our team, check out:

Security & PrivacySponsored Security Software
Free PowerShell & Sysadmin Toolkit

Get Our Sysadmin & AI Runbooks Direct to Your Inbox

Join 2,500+ engineers receiving our weekly PowerShell automation scripts, root cause analyses, and hardware diagnostic playbooks.

Zero spam. Unsubscribe anytime in 1 click.

Frequently Asked Questions: Microsoft Device Code Flow DEBULL Abuse: 3 Fixes (2026)

What is Microsoft 365 Device Code Flow phishing?
Device Code Flow phishing tricks users into entering a user-code at microsoft.com/devicelogin. Once authenticated on Microsoft's legitimate portal, the attacker receives the primary refresh token (PRT) and session tokens.
How do you disable Device Code Flow in Microsoft Entra ID?
Disable the OAuth 2.0 Device Authorization Grant under Entra ID App Registrations, or enforce Conditional Access policies blocking device code authentication for non-compliant devices.
Does blocking device code flow break developer PowerShell tools?
Yes, blocking device code flow affects legacy PowerShell modules and CLI scripts that lack interactive browser redirection. Dev tools should migrate to Service Principals or Managed Identities.
Why does multi-factor authentication (MFA) fail to prevent DEBULL token theft?
Because the victim logs in on Microsoft's legitimate portal, MFA successfully satisfies the identity verification prompt. However, the resulting token is delivered directly to the attacker's CLI session that initiated the device code request.

Official Technical References

  1. OAuth 2.0 Device Authorization Grant (RFC 8628) — IETF
  2. Microsoft Entra ID: Device Code Flow Security Guidance — Microsoft Learn
  3. CISA Alert: Threat Actors Exploiting OAuth Device Code Authentication — Cybersecurity & Infrastructure Security Agency
Get Independent Tech Benchmarks First

Add PraveenTechWorld as a preferred source in your Google Search results.

Prefer on Google
P
Praveen

IT ops lead in India. I break Windows, Android and self-hosted AI stacks on my workbench, then write down what actually fixed them.

Explore more: Browse all security guides or check related articles below.