Uncovering a New Device Code Phishing Campaign

Uncovering a New Device Code Phishing Campaign

Uncovering a phishing campaign abusing Microsoft Device Code Authentication and Cloudflare Worker Pages, with detection hunts for Entra and Microsoft 365.

Introduction

During some hunting in the wild, I came across a phishing campaign abusing Microsoft device code authentication. Abusing device code authentication for phishing is not new, but I’ve long wanted to do a post and write some hunts on the topic, and this campaign gave me a good opportunity.

The Campaign

The phishing email received contains a basic lure with a URL pointing to a Cloudflare workers.dev domain. The initial phish link includes a URI parameter of ?email=projectproposal, as shown here:

https://index-aj9[.]michelleteh-surgipro-com-sg-s-account[.]workers[.]dev/?email=projectproposal

This redirects to a second URI:

https://index-aj9[.]michelleteh-surgipro-com-sg-s-account[.]workers[.]dev/b8184e32fd933ee3

Which hosts the phishing page below.

Phishing page mimicking an Adobe document download
Figure 1: Phishing Page

Phish Page

As shown above, the phishing page mimics an Adobe document download page, telling the user they need to verify their identity with a verification code. A “Continue to Microsoft” button is included. When clicked, this opens a new browser window pointing to https://login.microsoftonline.com/common/oauth2/deviceauth.

Microsoft device authentication prompt
Figure 2: Microsoft Device Authentication Prompt

Device code is a legitimate Microsoft authentication mechanism, frequently abused by attackers. It is designed to allow input-constrained devices — such as IoT hardware — to authenticate against Entra. A legitimate example would be a Polycom device in an office that needs to authenticate to Microsoft Teams. A 9-character code is generated and entered on the device, which then completes authentication via a normal OAuth flow.

This is abused by threat actors due to its simplicity — they only need to coerce the user into entering a code into a legitimate Microsoft portal. The device code generated is tied to the attacker’s OAuth session, not the victim’s browser.

Phish Code

Let’s dig a little deeper into what is happening on the page. After capturing the browser DOM for a session to one of the phishing pages in the wild, we can see the following code.

Session Initialisation (sf())

Called after DOM load, this function contains an anti-bot token which is sent to the attacker’s backend with every request from this page. Requests without the token are rejected server-side.

const at = '55b64bc8170bcecd8dcc6514eaa39b528f0b9b7a87300931acca0a574c32f8c7'; // anti-bot token

async function sf() {
    const r = await fetch('/api/device/start', {
        method: 'POST',
        headers: { 'X-Antibot-Token': at }  // server-side bot filtering
    });
    const d = await r.json();
    // d = { userCode: "XXXX-XXXX", sessionId: "uuid" }

    document.getElementById('uc').textContent = d.userCode; // display real MS code
    sid = d.sessionId;                                       // store for polling

    // swap loading spinner → main UI
    document.getElementById('lds').classList.add('hidden');
    document.getElementById('dcs').classList.remove('hidden');

    pi = setInterval(ck, 3000); // start polling every 3s
}

The attacker’s backend handles the actual POST /oauth2/v2.0/devicecode call to Microsoft and returns the user_code to display, along with a sessionId to poll against. Polling is important because device codes expire after a short period (approximately 10 minutes), so the attacker’s backend continuously polls for a fresh code.

Token Capture Polling (ck())

The next function stops polling once the session is complete. “Completed” means the attacker’s backend has successfully exchanged the device code for an access token and refresh token from Microsoft. The victim is then quietly redirected to adobe.com with no indication that anything has happened.

async function ck() {
    const r = await fetch('/api/device/status/' + sid); // attacker's backend
    const d = await r.json();

    if (d.status === 'completed') {
        clearInterval(pi); // stop polling

        // show success screen briefly
        document.getElementById('dcs').classList.add('hidden');
        document.getElementById('scs').classList.remove('hidden');

        // redirect victim to real Adobe — removes suspicious page from history
        setTimeout(() => { window.location.replace('https://www.adobe.com') }, 2000);
    }
}

Code Copy + Microsoft Redirect (gm() / cc())

The popup window sizing is deliberate — a centred 500×600 window looks like a legitimate OAuth prompt. The device code is already copied to the clipboard before the popup opens, so the victim only needs to press Ctrl+V.

function cc() {
    // silently copies the device code to clipboard
    navigator.clipboard.writeText(document.getElementById('uc').textContent);
}

function gm() {
    cc(); // auto-copy before opening Microsoft

    const m = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) || window.innerWidth <= 768;

    if (m)
        window.open('https://microsoft.com/devicelogin', '_blank');        // mobile: new tab
    else
        window.open('https://microsoft.com/devicelogin', 'ML',             // desktop: popup
            'width=500,height=600,left=20,top=' + (screen.height - 600) / 2);
}

Sender Domain and the Workers.dev Naming Convention

One detail worth noting in this campaign is how the workers.dev phishing domains are constructed. They appear to include the sender email address in the phish URL.

{prefix}.{accountname}-s-account.workers.dev

This is likely deliberate, to make the phish URLs appear more believable by having them reflect the sender’s email address.

Sender Domain Reputation

The sender domains used in this campaign are all established, with registration dates ranging from 1995 to 2017 — giving them domain histories of between 9 and 31 years. Older domains carry higher inherent trust with both recipients and email security filters. The sender domains also make use of varied mail infrastructure: some are behind Microsoft 365, while others use mail servers provided by web hosting providers. Both of these suggest the sender domains to be victims of business email compromise,

Device Code Auths in Logs

I’m using my lab tenant to generate telemetry. On the attacker machine, a script mimics the attacker side — generating a device code and polling for its completion.

Attacker-side script polling for device code completion
Figure 3: Attacker Script Polling for Device Code Completion

Once the user enters the code at https://login.microsoftonline.com/common/oauth2/deviceauth, they are asked to verify the authentication. As shown below, the user is shown the application they are authenticating to as well as the location the authentication is coming from. Applications like Azure CLI are high-value targets due to the level of access they provide.

Microsoft prompt shown to the victim user during device code authentication
Figure 4: Victim Confirmation Prompt

Finding the Activity

I’m using a Log Analytics workspace to store my Entra logs and query them with KQL. Device code authentications can easily be found with the below query.

SigninLogs | where AuthenticationProtocol == "deviceCode"

We can see the authentications in the screenshot below.

Device code authentication events in Log Analytics
Figure 5: Device Code Authentications in Log Analytics

Sign-ins from unmanaged, non-Entra-registered devices should be treated as a major red flag.

Detection Hunts

Workers.dev Phishing URL in Email hunt-2025-041

To match on this campaign and similar ones, we can look for email URLs in Defender XDR containing workers.dev domains. These are uncommon in legitimate enterprise email and their presence should be investigated, particularly when correlated with device code authentication events from the same user.

General Device Code Authentication hunt-2025-042

This analytic surfaces all device code authentication events across the tenant, enriched with device compliance status, operating system, browser, country, and Entra risk scores. It provides the baseline visibility needed to detect abuse and is a good starting point for building a device code usage baseline.

First-Time Device Code Auth — No Prior History hunt-2025-043

This behavioural hunt uses a 30-day lookback to establish which users regularly authenticate via device code, then flags any user seen using the protocol for the first time in the past day. Limiting to ResultType == 0 (success) ensures we only surface completed authentications — meaning a token was actually issued. This is a high-confidence signal for phishing.

IOCs

Below are the known IOCs for the campaign.

Phishing Domains (workers.dev)

  • index-yv6[.]oliver-thevaluexchange-co-uk-s-account[.]workers[.]dev
  • index-j38[.]admin-ypo-bm-s-account[.]workers[.]dev
  • index-swq[.]alain-damasckinvestments-com-s-account[.]workers[.]dev
  • index-kgz[.]accounts-monacosolicitors-co-uk-s-account[.]workers[.]dev
  • index-q6t[.]lisa-trellisjewellery-co-uk-s-account[.]workers[.]dev
  • index-2ml[.]raygoodchild-pressandstarkey-com-s-account[.]workers[.]dev
  • index-vma[.]aparna-gulati-eshipfinance-com-s-account[.]workers[.]dev
  • index-pqx[.]mathieu-greppo-phoeniciabeirut-com-s-account[.]workers[.]dev
  • index-wnc[.]mtaj-leaddevelopment-ae-s-account[.]workers[.]dev
  • index-iko[.]bo-bo-rasmussen-com-s-account[.]workers[.]dev
  • index-doz[.]john-winter-ruxleyventures-com-s-account[.]workers[.]dev
  • index-x57[.]audrey-chan-burdaluxury-com-s-account[.]workers[.]dev
  • index-dzd[.]olivia-nextboatworks-com-s-account[.]workers[.]dev
  • index-r61[.]yvonne-womeninfootball-co-uk-s-account[.]workers[.]dev
  • index-n90[.]jean-louis-weemaes-skyebase-be-s-account[.]workers[.]dev
  • index-4f7[.]iswira-trlaw-co-id-s-account[.]workers[.]dev
  • index-aj9[.]michelleteh-surgipro-com-sg-s-account[.]workers[.]dev
  • index-fkz[.]gavint-cibcommunications-co-uk-s-account[.]workers[.]dev
  • index-dz5[.]ola-abdelraouf-torjoman-com-s-account[.]workers[.]dev
  • index-zim[.]abell-servproofnewhanover-com-s-account[.]workers[.]dev
  • index-lb3[.]sam-butlersherborn-co-uk-s-account[.]workers[.]dev
  • index-955[.]steve-philanthrope-co-uk-s-account[.]workers[.]dev
  • index-2so[.]aarathe-ramraj-tipgroup-com-au-s-account[.]workers[.]dev
  • index-mhq[.]sandra-solorzano-duncanfamilyfarms-net-s-account[.]workers[.]dev

Suspected Sender Email Addresses

The sender addresses below are inferred from the Cloudflare account names embedded in the workers.dev subdomains. The exact local-part format (e.g. firstname.lastname vs firstnamelastname) may vary.

  • oliver[@]thevaluexchange[.]co[.]uk
  • admin[@]ypo[.]bm
  • alain[@]damasckinvestments[.]com
  • accounts[@]monacosolicitors[.]co[.]uk
  • lisa[@]trellisjewellery[.]co[.]uk
  • raygoodchild[@]pressandstarkey[.]com
  • aparna.gulati[@]eshipfinance[.]com
  • mathieu.greppo[@]phoeniciabeirut[.]com
  • mtaj[@]leaddevelopment[.]ae
  • bo.bo[@]rasmussen[.]com
  • john.winter[@]ruxleyventures[.]com
  • audrey.chan[@]burdaluxury[.]com
  • olivia[@]nextboatworks[.]com
  • yvonne[@]womeninfootball[.]co[.]uk
  • jean-louis.weemaes[@]skyebase[.]be
  • iswira[@]trlaw[.]co[.]id
  • michelleteh[@]surgipro[.]com[.]sg
  • gavint[@]cibcommunications[.]co[.]uk
  • ola.abdelraouf[@]torjoman[.]com
  • abell[@]servproofnewhanover[.]com
  • sam[@]butlersherborn[.]co[.]uk
  • steve[@]philanthrope[.]co[.]uk
  • aarathe.ramraj[@]tipgroup[.]com[.]au
  • sandra.solorzano[@]duncanfamilyfarms[.]net

Email Subject

The observed subject line follows the pattern:

Project Proposal - [Company Name]

For example: Project Proposal - Monaco Solicitors Ltd

Resources