Terrance DeJesus

Microsoft Entra ID OAuth Phishing and Detections

A crash course on in-the-wild (ItW) Entra ID OAuth phishing and detection strategies

Microsoft Entra ID OAuth Phishing and Detections

Preamble

Members of the Threat Research and Detection Engineering (TRADE) team at Elastic have recently turned their attention to an emerging class of threats targeting OAuth workflows in Microsoft Entra ID (previously Azure AD). This research was inspired by Volexity's recent blog, Phishing for Codes: Russian Threat Actors Target Microsoft 365 OAuth Workflows, which attributes a sophisticated OAuth phishing campaign against NGOs to the threat actor designated UTA0352.

Volexity's investigation presents compelling forensic evidence of how attackers abused trusted first-party Microsoft applications to bypass traditional defenses. Using legitimate OAuth flows and the open-source tool ROADtools, the actors crafted customized Microsoft authentication URLs, harvested security tokens and leveraged them to impersonate users, elevate privilege, and exfiltrate data via Microsoft Graph — including downloading Outlook emails and accessing SharePoint sites.

While their report thoroughly documents the what of the attack, our team at Elastic focused on understanding the how. We emulated the attack chain in a controlled environment to explore the mechanics of token abuse, device registration, and token enrichment firsthand. This hands-on experimentation yielded deeper insights into the inner workings of Microsoft's OAuth implementation, the practical use of ROADtools, recommended mitigations, and most importantly, effective detection strategies to identify and respond to similar activity.

OAuth in Microsoft Entra ID

Microsoft Entra ID implements OAuth 2.0 to enable delegated access to Microsoft 365 services like Outlook, SharePoint, and Graph API. While the OAuth specification is standardized (RFC6749), Entra ID introduces unique behaviors and token types that influence how delegated access works and how adversaries exploit them.

In delegated access, an application is authorized to act on behalf of a signed-in user, constrained by scopes (permissions) the app requests and the user or admin consents to. This model is common in enterprise environments where apps retrieve a user's emails, files, or directory data without prompting for credentials each time.

A typical delegated authorization flow includes:

Authorization request (OAuth 2.0 Authorization Code Grant): The app requests access to a resource (e.g., Graph) with specific scopes (e.g., Mail.Read, offline_access). These are added as parameters to the URI.

  • client_id: The application’s ID (e.g., VSCode)
  • Response_type: Determines the grant type OAuth workflow (e.g. device code, auth code)
  • Scope: Permissions requested for the target resource (e.g. Mail.Read, offline_access)
  • Redirect_uri: Where to send our authorization codes
  • State: CSRF protection
  • Login_hint: Pre-fills username

User authentication (OpenID Connect): Entra ID authenticates the user via policy (password, MFA, device trust).

  • Single-Factor Authentication (SFA)
  • Multi-factor Authentication (MFA)
  • Device Trust (Hybrid Join, Intune compliance)
  • Conditional Access Policies (CAP)
  • Single Sign-On (SSO)

Consent: Consent governs whether the app can receive an authorization code and what scopes are permitted.

  • User-consentable scopes (e.g. Mail.Read, offline_access)
  • Admin-Consent required scopes (e.g. Directory.ReadWrite) requires elevated approval.

Token issuance: The app receives an authorization code, then redeems it for :

  • Access Token – short-lived token used to call APIs like Graph.
  • Refresh Token (RT) – longer-lived token to obtain new access tokens silently.
  • Identity Token - Describes authenticated user; present in OpenID flows.
  • (Optional) Primary Refresh Token: If the user is on a domain-joined or registered device, a Primary Refresh Token (PRT) may enable silent SSO and additional token flows without user interaction.
  • Token claims: Claims are key-value pairs embedded in JWT tokens that describe the user, app, device, scopes and context of the authentication.

What Defines an MSFT OAuth Phishing URL

Before diving into key findings from Volexity's report that help shape our detection strategy, it's important to break down what exactly defines a Microsoft OAuth phishing URL.

As described earlier, Microsoft Entra ID relies on these URLs to determine which application (client) is requesting access, on behalf of which user principal, to what resource, and with what permissions. Much of this context is embedded directly in the query parameters of the OAuth authorization request, making them a critical source of metadata for both adversaries and defenders.

Here's an example of a phishing URL aligned with the authorization code grant flow, adapted from Volexity's blog:

https://login.microsoftonline[.]com/organizations/oauth2/v2.0/authorize?state=https://mae.gov[.]ro/[REMOVED]&client_id=aebc6443-996d-45c2-90f0-388ff96faa56&scope=https://graph.microsoft.com/.default&response_type=code&redirect_uri=https://insiders.vscode.dev/redirect&login_hint=<EMAIL HERE>

Let's break down some of the key components:

  • login.microsoftonline.com – The global Microsoft Entra ID authentication endpoint.
  • /oauth2/v2.0/authorize - MSFT Entra ID OAuth v2.0 endpoint for authorization workflows
  • state – Optional value used to prevent CSRF and maintain application state. Sometimes abused to obfuscate phishing redirections.
  • client_id – The application ID making the request. This could belong to Microsoft first-party apps (like VSCode, Teams) or malicious third-party apps registered by adversaries.
  • scope – Defines the permissions the application is requesting (e.g., Mail.Read, offline_access). The .default scope is often used for client credential flows to get pre-consented permissions.
  • response_type=code – Indicates the flow is requesting an authorization code, which can later be exchanged for an access and/or refresh token.
  • redirect_uri – Where Entra ID will send the response after the user authenticates. If an attacker controls this URI, they gain the code or it is a MSFT-managed URI that is valid.
  • login_hint – Specifies the target user (e.g., alice @ tenant.onmicrosoft.com). Often pre-filled to lower friction during phishing.

Note: While this example illustrates a common Microsoft Entra ID OAuth phishing URL, there are many variations. Adversaries may adjust parameters such as the client ID, scopes, grant types or redirect URIs depending on their specific objectives, whether it's to gain persistent access, exfiltrate emails, or escalate privileges via broader consent grants.

Why Does This Matter?

Because these parameters are customizable, adversaries can easily swap out values to suit their operation. For example:

  • They might use a legitimate Microsoft client ID to blend in with benign applications.
  • They may use a .default scope to bypass specific consent prompts.
  • They’ll point the redirect_uri to a site under their control to collect the authorization code.
  • They can target specific user principals they may have identified during reconnaissance.
  • They can adjust permissions to target resources based on their operational needs.

Once a target authenticates, the goal is simple – obtain an authorization code. This code is then exchanged (often using tools like ROADtools) for a refresh token and/or access token, enabling the attacker to make Graph API calls or pivot into other Microsoft 365 services, all without further user interaction.

Abstraction of Volexity's Key Findings

For threat detection, it is critical to understand the protocols like OAuth, workflow implementation in Microsoft Entra ID, and contextual metadata about the behaviors and/or steps taken by the adversary regarding this operation.

From Volexity's investigation and research, we can key in the different variations of OAuth phishing reported. We decided to break these down for easier understanding:

OAuth Phishing To Access Graph API as VSCode Client On-Behalf-Of Target User Principal: These URLs are similar to our example “What Defines an MSFT OAuth Phishing URL” – the end game goal being an access token to Graph API with default permissions.

  • OAuth phishing URLs were custom, pointing to "authorize" endpoint
  • Client IDs were specifically VSCode ("aebc6443-996d-45c2-90f0-388ff96faa56")
  • Resource/Scope was MSFT Graph ("https://graph.microsoft.com/.default") with .default permissions
  • Token grant flows were auth code (response_type=code)
  • Redirect URIs were for legitimate MSFT domains (insiders[.]vscode[.]dev or vscode-redirect[.]azurewebsites[.]net)
  • Login hints were the specific user principal being targeted (not service principals)
  • Adversary required the target to open the URL, authenticate and share the authorization code (1.AXg….)

From here, the adversary would be able to make a request to MSFT's OAuth token endpoint (https://login.microsoftonline.com/[tenant_id]/oauth2/v2.0/token) and exchange the refresh token for an access token. This is enough to allow the adversary to access Graph API and access resources normally available to the user. These indicators will be crucial to factoring our detection and hunting strategies later on in this blog.

OAuth Phishing for Device Registration as MSFT Auth Broker: These URLs are unique as they are chained with subsequence ROADtools usage to register a virtual device, exchange an RT for a PRT, and require PRT enrichment to accomplish email access via Graph API and Sharepoint access.

  • OAuth phishing URLs were custom, pointing to authorize (https://login.microsoftonline.com/[tenant_id]/oauth2/v2.0/authorize) endpoint
  • Client IDs were specifically MSFT Authentication Broker ("29d9ed98-a469-4536-ade2-f981bc1d605e")
  • Resource/Scope was Device Registration Service (DRS) ("01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9")
  • Token grant flows were auth code (response_type=code)
  • Redirect URI includes cloud-based domain join endpoint (typically used during Windows setup or Autopilot)
  • Login hint contains user principal email address (Target)
  • Request is ultimately for an ADRS token

If the user is phished and opens the URL, authenticating will provide an ADRS token that is required for the adversary to register a device and subsequently obtain a PRT with the device’s private key and PEM file.

Volexity's blog also includes additional information about tracking the activity of the compromise identity via the device ID registered as well as post-compromise activity following an approved 2FA request was identified, allowing the adversary to download the target's email with a session tied to the newly registered device.

With this understanding of each phishing attempt, our next goal is to replicate this in our own MSFT tenant as accurately as possible to gather data for plausible detections.

Our Emulation Efforts

Alright – so at this point, we’ve covered the fundamentals of OAuth and how Microsoft Entra ID implements it. We broke down what defines a Microsoft OAuth phishing URL, decoded its critical parameters, and pulled key insights from Volexity's excellent investigation to identify indicators aligned with these phishing workflows.

But theory and a glimpse into Volexity's notebook only takes us so far.

To truly understand the attacker's perspective, the full chain of execution, tooling quirks, subtle pitfalls, and opportunities for abuse, we decided to go hands-on with whitebox testing. We recreated the OAuth phishing process in our own tenant, emulating everything from token harvesting to resource access. The goal? Go beyond static indicators and surface the behavioral breadcrumbs that defenders can reliably detect.

Let's get into it.

Prerequisites

For starters, it is good to share some details about our threat research and detection environment in Azure.

  • Established Azure tenant: TENANT.onmicrosoft.com
  • Established Sharepoint Domain: DOMAIN.sharepoint.com
  • Native IdP Microsoft Entra ID – Enabling our IAM
  • Microsoft 365 Licenses (P2) for All Users
  • Azure Activity Logs Streaming to EventHub
  • Microsoft Entra ID Sign-In Logs Streaming to EventHub
  • Microsoft Entra ID Audit Logs Streaming to EventHub
  • Microsoft Graph Audit Logs Streaming to EventHub
  • Microsoft 365 Audit Logs Streaming to EventHub
  • Elastic Azure and M365 Integration Enabled for Log Digestion from EventHub
  • Basic Admin User Enabled with CAP Requiring MFA
  • MSFT Authenticator App on Mobile for 2FA Emulation
  • Windows 10 Desktop with NordVPN (Adversary Box)
  • macOS endpoint (Victim box)

Note that while we could follow the workflows from a single endpoint, often we need data that reflects separate source addresses to developer detection variations of impossible travel.

Scenario 1: OAuth Phishing as VSCode Client

Emulation

To emulate the phishing technique documented by Volexity, we built a Python script to generate an OAuth 2.0 authorization URL using Microsoft Entra ID. The URL initiates an authorization code grant flow, impersonating the first-party Visual Studio Code app to request delegated access to the Microsoft Graph API.

We configured the URL with the following parameters:

{
  "client_id": "aebc6443-996d-45c2-90f0-388ff96faa56",
  "response_type": "code",
  "redirect_uri": "insiders.vscode.dev/redirect",
  "scope": "https://graph.microsoft.com/.default",
  "login_hint": "user @ tenant.onmicrosoft.com",
  "prompt": "select_account",
  "state": "nothingtoseehere"
}

Figure 1: Parameters for OAuth Phishing URL

This URL is shared with the target (in our case, a MacOS test user). When opened, it authenticates the user and completes the OAuth workflow. Using browser developer tools, we capture the authorization code returned in the redirect URI, exactly what the attackers asked their victims to send back.

After receiving the code, we issue a POST request to:

{token_url: "https://login.microsoftonline.com/organizations/oauth2/v2.0/token"}

This exchange uses the authorization_code grant type, passing the code, client ID, and redirect URI. Microsoft returns an access token, but no refresh token. You might ask why that is?

The scope https://graph.microsoft.com/.default instructs Microsoft to issue a bearer token for all Graph permissions already granted to the VSCode app on behalf of the user. This is a static scope, pulling from the app registration, it does not include dynamic scopes like Mail.Read or offline_access.

Microsoft's documentation states:

““Clients can’t combine static (.default) consent and dynamic consent in a single request.””

Therefore, trying to include offline_access alongside .default results in an error. If the attacker wants a refresh token, they must avoid .default and instead explicitly request offline_access and the required delegated scopes (e.g., Mail.Read) – Assuming the app registration supports those.

With the access token in hand, we pivoted to a second script to interact with the Microsoft Graph API. The goal – extract email messages from the victim’s account — just as the attacker would.

To do this, we included the access token as a Bearer JWT in the authorization header and made a GET request to the following endpoint:

{graph_url: "https://graph.microsoft.com/v1.0/me/messages"}

The response returns a JSON array of email objects. From here, we simply iterate through the results and parse out useful metadata such as sender, subject, and received time.

To test the token’s broader privileges, we also attempted to enumerate SharePoint sites using:

{graph_search_url: "https://graph.microsoft.com/v1.0/sites?search=*"}

The request failed with an access denied error – which leads us to an important question: why did email access work, but SharePoint access did not? The reason is that the first-party client (VSCode: aebc6443-996d-45c2-90f0-388ff96faa56) does not have default delegated permissions with Graph for Sharepoint – as predefined by Microsoft. Therefore, we know the adversary is limited on what they can access.

To ensure this was accurate, we decoded the access token to identify the SCP associated with VSCode with .default permissions to Graph – Verifying no Sites.* permissioned by Microsoft.

This is one of the variations described by Volexity, but does help us understand more about the processes behind the scenes for the adversary – as well as resources, OAuth, and more for Microsoft Entra ID.

With the emulation complete, we now turn to identifying high-fidelity signals that are viable for SIEM detection and threat hunting. Our focus is on behavior observables in Microsoft Entra ID and Microsoft Graph logs.

Detection

Signal 1 - Microsoft Entra ID OAuth Phishing as Visual Studio Code Client

A successful OAuth 2.0 (authorization) and OpenID Connect (authentication) flow was completed using the first-party Microsoft application Visual Studio Code (VSCode). The sign-in occurred on behalf of the phished user principal, resulting in delegated access to Microsoft Graph with .default permissions.

event.dataset: "azure.signinlogs" and
event.action: "Sign-in activity" and
event.outcome: "success" and
azure.signinlogs.properties.user_type: "Member" and
azure.signinlogs.properties.authentication_processing_details: *Oauth* and
azure.signinlogs.category: "NonInteractiveUserSignInLogs" and
(
  azure.signinlogs.properties.resource_display_name: "Microsoft Graph" or
  azure.signinlogs.properties.resource_id: "00000003-0000-0000-c000-000000000000"
) and (
  azure.signinlogs.properties.app_id: "aebc6443-996d-45c2-90f0-388ff96faa56" or
  azure.signinlogs.properties.app_display_name: "Visual Studio Code"
)

Signal 2 - Microsoft Entra Session Reuse with Suspicious Graph Access

While traditional query languages like KQL are excellent for filtering and visualizing individual log events, they struggle when a detection relies on correlating multiple records across datasets, time, and identifiers. This is where ES|QL (Elasticsearch Query Language) becomes essential. These types of multi-event correlations, temporal logic, and field normalization are difficult or entirely impossible in static filter-based query languages like KQL without writing multiple disjointed queries and manually correlating them after the fact.

This detection relies on correlating multiple events that happen close together but from different data sources, namely sign-in logs and Microsoft Graph activity. The goal is to find suspicious reuse of the same session ID across multiple IPs, potentially indicating session hijacking or token abuse. For the sake of space regarding this publication, you can view the actual detection rule in the Detection Rules section. To better illustrate the flow of the query and meaning, below is a diagram to illustrate at a higher level.

[ FROM logs-azure.* ]
        |
        |  ← Pulls events from all relevant Microsoft Cloud datasets:
        |     - azure.signinlogs (authentication)
        |     - azure.graphactivitylogs (resource access)
        ↓
[ WHERE session_id IS NOT NULL AND IP NOT MICROSOFT ASN ]
        |
        |  ← Filters out Microsoft-owned infrastructure (e.g., internal proxy,
        |     Graph API relays) using ASN checks.
        |  ← Ensures session ID exists so events can be correlated together.
        ↓
[ EVAL session_id, event_type, time_window, etc. ]
        |
        |  ← Normalizes key fields across datasets:
        |     - session_id (from signin or Graph)
        |     - user ID, app ID, event type ("signin" or "graph")
        |  ← Buckets events into 5-minute windows using DATE_TRUNC()
        ↓
[ KEEP selected fields ]
        |
        |  ← Retains only what's needed:
        |     session_id, timestamp, IP, user, client ID, etc.
        ↓
[ STATS BY session_id + time_window ]
        |
        |  ← Groups by session and time window to compute:
        |     - unique IPs used
        |     - apps involved
        |     - first and last timestamps
        |     - whether both signin and graph occurred
        ↓
[ EVAL time_diff + signin_to_graph_delay ]
        |
        |  ← Calculates:
        |     - time_diff: full session duration
        |     - delay: gap between signin and Graph access
        ↓
[ WHERE types_count > 1 AND unique_ips > 1 AND delay <= 5 ]
        |
        |  ← Flags sessions where:
        |     - multiple event types (signin + graph)
        |     - multiple IPs used
        |     - all occurred within 5 minutes
        ↓
[ Output = Suspicious Session Reuse Detected ]

Signal 3 - Microsoft Entra ID Concurrent Sign-Ins with Suspicious Properties

This detection identifies suspicious sign-ins in Microsoft Entra ID where a user authenticates using the device code flow without MFA or sign-ins using the VSCode client. When the same identity signs in from two or more distinct IPs within a short time window using either method, it may indicate token replay, OAuth phishing, or adversary-in-the-middle (AitM) activity.

[ FROM logs-azure.signinlogs* ]
        |
        |  ← Pulls only Microsoft Entra ID sign-in logs
        ↓
[ WHERE @timestamp > NOW() - 1h AND event.outcome == "success" ]
        |
        |  ← Filters to the last hour and keeps only successful sign-ins
        ↓
[ WHERE source.ip IS NOT NULL AND identity IS NOT NULL ]
        |
        |  ← Ensures the sign-in is tied to a user and IP for correlation
        ↓
[ KEEP fields: identity, app_id, auth_protocol, IP, etc. ]
        |
        |  ← Retains app/client, IP, auth method, and resource info
        ↓
[ EVAL detection flags ]
        |
        |  ← Labels events as:
        |     - device_code: if MFA not required
        |     - visual_studio: if VS Code client used
        |     - other: everything else
        ↓
[ STATS BY identity ]
        |
        |  ← Aggregates all sign-ins per user, calculates:
        |     - IP count
        |     - Device Code or VSCode usage
        |     - App/client/resource details
        ↓
[ WHERE src_ip >= 2 AND (device_code_count > 0 OR vsc > 0) ]
        |
        |  ← Flags users with:
        |     - Sign-ins from multiple IPs
        |     - And either:
        |         - Device Code w/o MFA
        |         - Visual Studio Code app
        ↓
[ Output = Potential OAuth Phishing or Token Misuse ]

While this variation of OAuth phishing lacks the full persistence offered by refresh tokens or PRTs, it still provides adversaries with valuable one-time access to sensitive user data – such as emails – through legitimate channels. This exercise helps us understand the limitations and capabilities of static .default scopes, the influence of app registrations, and how Microsoft Graph plays a pivotal role in post-authentication. It also reinforces a broader lesson: not all OAuth phishing attacks are created equal. Some aim for longevity (as we will see later) through refresh tokens or device registration, while others focus on immediate data theft via first-party clients. Understanding the nuances is essential for accurate detection logic.

Scenario 2: OAuth Phishing for Device Registration

As we stated earlier – Volexity also reported a separate phishing playbook targeting victims, this time with the goal of registering a virtual device and obtaining a PRT. While this approach requires more steps from the adversary, the payoff is a token-granting token that offers far more utility for completing their operations. For our emulation efforts, we needed to expand our toolset and rely on ROADtools, just as the adversary did to remain accurate, however, several other python scripts were made for initial phishing and post-compromise actions.

Emulation

Starting with the initial phishing, we adjusted our Python script to craft a different OAuth URL that would be sent to our victim. This time, the focus was on our first-party client ID being the Microsoft Authentication Broker, requesting a refresh token with offline_access and redirecting to Entra ID’s cloud domain device joining endpoint URI.

{
  "client_id": "29d9ed98-a469-4536-ade2-f981bc1d605e",
  "response_type": "code",
  "response_mode": "query",
  "redirect_uri": "https://login.microsoftonline.com/WebApp/CloudDomainJoin/8",
  "resource": "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9",
  "state": "nothingtoseehere"
}

If successful and our victim authenticates, the OAuth workflow will complete and the user will be redirected to the specified URI with an appended authorization code in the query parameters. Again, this code is the critical piece, it must be shared back with the adversary in order to exchange it for tokens. In our case, once the phishing URL is opened and the target authenticates, we capture the authorization code embedded in the redirect and use it to request tokens from the Microsoft Entra ID token endpoint.

Now, here's where it gets interesting. In response to the token request, we receive three types of tokens: an access token, a refresh token, and an ID token. You might be asking – why do we get more than just an access token? The answer lies in the scopes we initially requested: openid, offline_access, and profile.

  • openid grants us an ID token, which is part of the OpenID Connect layer and confirms the identity of the user — this is your authentication (authN) artifact.
  • offline_access provides a refresh token, enabling us to maintain a session and request new access tokens without requiring re-authentication, this supports persistent access but is critical for our use with ROADtx.
  • And the access token itself is used to authorize requests to protected APIs like Microsoft Graph, this represents authorization (authZ).

With these three tokens, we have everything: authentication, authorization, and long-term session continuity. That’s enough to shift from a simple OAuth phishing play into a more persistent foothold — like registering a new device in Microsoft Entra ID.

Now let’s connect the dots. A PRT requires registration of a valid device, one that Entra ID recognizes via a device certificate and private key. This is where ROADtx comes into play. Because our initial OAuth phishing impersonated a joined device flow, and the client used was the Microsoft Authentication Broker (a first-party client that interacts with the Device Registration Service), we already have the right access token in hand to interact with DRS. Notice in our returned object the scope is adrs_access which indicates Azure DRS access and is important for detections later.

From here, we simply drop the JSON object received from our token exchange into the .roadtool_auth file. This file is natively consumed by ROADtools, which uses the stored tokens to perform the device registration, completing the adversary’s move into persistence and setting the stage for obtaining a valid PRT.

After obtaining the tokens, we prep them for ROADtx by reformatting the JSON. ROADtx expects keys in camelCase, and we must also include the Microsoft Authentication Broker’s client ID as _clientId. This setup allows us to run the refreshtokento command, which takes our refresh token and exchanges it for a new JWT scoped to the DRS — specifically, the service principal urn:ms-drs:enterpriseregistration.windows.net.

Once that’s in place, we use the device command to simulate a new device registration. This operation doesn’t require any actual virtual machine or physical host because it’s a backend registration that simply creates an entry in Entra ID. Upon success, we’re issued a valid device ID, PEM-encoded certificate, and private key — all of which are required to simulate a valid hybrid-joined device in the Microsoft ecosystem.

With our device identity established, we invoke the prt command. This uses the refresh token, device certificate, and private key to mint a new PRT — a highly privileged credential that effectively ties together user and device trust in Microsoft Entra ID.

And just like that — whollah! — we have a PRT.

But why go through all this? Why register a device, generate a cert, and obtain a PRT when we already had an access token, ID token, and refresh token?

Because the PRT is the key to full user and device identity emulation. Think of it as a Kerberos-like ticket-granting token in Entra ID’s world, but instead – a token-granting token. With a valid PRT:

  • An adversary can request new access and ID tokens for first-party apps like Outlook, SharePoint, or Teams without needing user interaction.
  • The PRT enables seamless single sign-on SSO across multiple services, bypassing MFA and other conditional access policies (CAP) that would typically re-prompt the user. This is crucial for persistence as CAP and MFA are often huge barriers for adversaries.
  • It supports long-lived persistence, as the PRT can be silently renewed and leveraged across sessions as long as the device identity remains trusted.

And perhaps most dangerously — the PRT allows adversaries to impersonate a fully compliant, domain-joined device and user combo, effectively bypassing most conventional detection and response controls making the line between benign vs suspicious extremely thin for hunters and analysts.

This makes the PRT an incredibly valuable asset or one that enables covert lateral movement, privilege escalation, and deep access to Microsoft 365 services. It’s not just about getting in anymore — it’s about staying undetected.

Let’s not forget post-compromise activity…

ROADtx offers a few powerful commands frequently used by adversaries – prtenrich and browserprtauth. For example, we can access most browser-based UI services in the Microsoft suite by supplying our PRT which includes the necessary metadata for authentication and authorization – which originally belonged to our phishing victim (me), but is actually the Microsoft Authentication Broker acting on their behalf.

Volexity also reported that following device registration and the PRT acquisition – a 2FA request was sent to the initial victim, approved and then used to access emails via SharePoint. While they do not specify exactly how requests were made to – it’s reasonable to assume the adversary used the PRT to authenticate via a first-party Microsoft client – with the actual data access happening through Microsoft Graph. Graph remains a popular target post-compromise because it serves as a central API hub for most Microsoft 365 resources.

To start – let’s leverage ROADtx to auth with our PRT where Microsoft Teams is our client and Microsoft Graph is our resource. When using the prtauth command with our PRT, we are able to obtain a new access token and refresh token – clearly demonstrating the utility of the PRT as a token-granting token within Microsoft’s identity fabric.

Once our access token is obtained, we plug it into a custom Python script to start enumerating our SharePoint sites, drives, items which allows us to identify files of interest and download their contents.

With this emulation – we showed how adversaries can chain OAuth phishing with the Microsoft Authentication Broker and obtain necessary credential material to leverage ROADtx for acquiring a PRT. This PRT then being an important utility post-compromise to access sensitive files, enumerate tenant resources and much more.

Now, let’s shift focus: what are plausible and accurate signals for detecting this activity?

Detection

Signal 1 - Microsoft Entra ID OAuth Phishing as Microsoft Authentication Broker

Identifies instances where a user principal initiates an OAuth authorization code flow using the Microsoft Authentication Broker (MAB) as the client and the Device Registration Service (DRS) as the target resource. This detection focuses on cases where a single session ID is reused across two or more distinct IP addresses within a short time window, and at least one request originates from a browser — behavior commonly associated with phishing.

[ FROM logs-azure.signinlogs-* ]
        |
        |  ← Pulls all Microsoft Entra ID sign-in logs
        ↓
[ WHERE app_id == MAB AND resource_id == DRS ]
        |
        |  ← Filters to OAuth auth code requests targeting
        |     Microsoft Authentication Broker + Device Reg Service
        ↓
[ EVAL session_id + is_browser ]
        |
        |  ← Extracts session ID and flags browser-based activity
        ↓
[ STATS BY 30-minute window, user, session_id ]
        |
        |  ← Groups logins within same session and time window,
        |     then aggregates:
        |       - user/session/token identifiers
        |       - distinct IPs and geo info
        |       - user agent, browser presence
        |       - app/resource/client info
        ↓
[ WHERE ip_count ≥ 2 AND session_id_count == 1 ]
        |
        |  ← Identifies reuse of a single session ID
        |     across ≥ 2 different IP addresses
        ↓
[ AND has_browser ≥ 1 AND auth_count ≥ 2 ]
        |
        |  ← Requires at least one browser-based request
        |     and at least two total sign-in events
        ↓
[ Output = Suspicious OAuth Flow with Auth Broker for DRS ]

Signal 2 - Suspicious ADRS Token Request by Microsoft Auth Broker

Identifies Microsoft Entra ID sign-in events where a user principal authenticates using a refresh token issued to the Microsoft Authentication Broker (MAB) client, targeting the Device Registration Service (DRS) with the adrs_access OAuth scope. This pattern may indicate token-based access to DRS following an initial authorization code phishing or device registration flow.

event.dataset: "azure.signinlogs" and azure.signinlogs.properties.app_id : "29d9ed98-a469-4536-ade2-f981bc1d605e" and azure.signinlogs.properties.resource_id : "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9" and azure.signinlogs.properties.authentication_processing_details.`Oauth Scope Info`: *adrs_access* and azure.signinlogs.properties.incoming_token_type: "refreshToken" and azure.signinlogs.properties.user_type: "Member"

Signal 3 - Unusual Device Registration in Entra ID

Detects a sequence of Entra ID audit log events indicating potential malicious device registration activity using a refresh token, commonly seen after OAuth phishing. This pattern mimics the behavior of tools like ROADtx, where a newly registered Windows device (with a hardcoded OS version 10.0.19041.928) is added by the Device Registration Service, followed by user and owner assignments. All events must share the same correlation ID and occur within a one-minute window, strongly suggesting automation or script-driven registration rather than legitimate user behavior.

sequence by azure.correlation_id with maxspan=1m
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.identity == "Device Registration Service" and azure.auditlogs.operation_name == "Add device" and azure.auditlogs.properties.additional_details.value like "Microsoft.OData.Client/*" and (
  azure.auditlogs.properties.target_resources.`0`.modified_properties.`1`.display_name == "CloudAccountEnabled" and 
azure.auditlogs.properties.target_resources.`0`.modified_properties.`1`.new_value: "[true]") and azure.auditlogs.properties.target_resources.`0`.modified_properties.`3`.new_value like "*10.0.19041.928*"]
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.operation_name == "Add registered users to device" and azure.auditlogs.properties.target_resources.`0`.modified_properties.`2`.new_value like "*urn:ms-drs:enterpriseregistration.windows.net*"]
[any where event.dataset == "azure.auditlogs" and azure.auditlogs.operation_name == "Add registered owner to device"]

Signal 3 - Entra ID RT to PRT Transition from Same User and Device

This detection identifies when a Microsoft Entra ID user first authenticates using a refresh token issued to the Microsoft Authentication Broker (MAB), followed shortly by the use of a Primary Refresh Token (PRT) from the same device. This sequence is rare in normal user behavior and may indicate an adversary has successfully registered a device and escalated to persistent access using tools like ROADtx. By filtering out activity tied to the Device Registration Service (DRS) in the second step, the rule focuses on post-registration usage of the PRT to access other Microsoft 365 services.

This behavior strongly suggests token-based compromise and long-term session emulation, particularly when device trust is established silently. Catching this transition from refresh token to PRT is critical for surfacing high-fidelity signals of OAuth phishing and post-compromise persistence.

sequence by azure.signinlogs.properties.user_id, azure.signinlogs.properties.device_detail.device_id with maxspan=1d
  [authentication where 
    event.dataset == "azure.signinlogs" and
    azure.signinlogs.category == "NonInteractiveUserSignInLogs" and
    azure.signinlogs.properties.app_id == "29d9ed98-a469-4536-ade2-f981bc1d605e" and
    azure.signinlogs.properties.incoming_token_type == "refreshToken" and
    azure.signinlogs.properties.device_detail.trust_type == "Azure AD joined" and
    azure.signinlogs.properties.device_detail.device_id != null and
    azure.signinlogs.properties.token_protection_status_details.sign_in_session_status == "unbound" and
    azure.signinlogs.properties.user_type == "Member" and
    azure.signinlogs.result_signature == "SUCCESS"
  ]
  [authentication where 
    event.dataset == "azure.signinlogs" and
    azure.signinlogs.properties.incoming_token_type == "primaryRefreshToken" and
    azure.signinlogs.properties.resource_display_name != "Device Registration Service" and
    azure.signinlogs.result_signature == "SUCCESS"
  ]

Signal 4 - Unusual PRT Usage and Registered Device for User Principal

This detection surfaces when a Microsoft Entra ID user registers a new device not previously seen within the last 7 days – behavior often associated with OAuth phishing campaigns that chain into ROADtx-based device registration. In these attacks, adversaries trick users into authorizing access for the Microsoft Authentication Broker (MAB) targeting the DRS, obtain a RT, and then use ROADtx to silently register a fake Windows device and mint a PRT. This rule alerts when a user principal authenticates from a newly observed device ID, particularly if the session is unbound, which is characteristic of token replay or device spoofing. Because PRTs require a registered and trusted device, this signal plays a critical role in identifying when an adversary has crossed from basic token abuse into persistent, stealthy access aligned with long-term compromise.

event.dataset: "azure.signinlogs" and
    event.category: "authentication" and
    azure.signinlogs.properties.user_type: "Member" and
    azure.signinlogs.properties.token_protection_status_details.sign_in_session_status: "unbound" and
    not azure.signinlogs.properties.device_detail.device_id: "" and
    azure.signinlogs.properties.user_principal_name: *

New Terms Values:

  • azure.signinlogs.properties.user_principal_name
  • azure.signinlogs.properties.device_detail.device_id

This emulation helped us validate the full attacker workflow – from phishing for consent to establishing device trust and minting a PRT for long-term persistence. By chaining OAuth abuse with device registration, adversaries can satisfy CAPs, impersonate compliant endpoints and move laterally through cloud environments – often without triggering traditional security controls.

These nuances matter. When viewed in isolation, individual events like token issuance or device registration may appear benign. But when correlated across sign-in logs, audit data and token metadata, they expose a distinct trail of identity compromise.

Key Telemetry Details for Detection and Abuse

Throughout our emulation and detection efforts, specific telemetry artifacts consistently proved essential for separating benign OAuth activity from malicious abuse. Understanding how these fields appear in Microsoft Entra ID logs – and how attackers manipulate them – is critical for effective hunting and detection engineering. From client IDs and grant types to device compliance, token types and conditional access outcomes, these signals tell the story of identity-based attacks. Below we have curated a list of those most important and how they can enable us.

Client Application IDs (client_id): Identify the application initiating the OAuth request. First-party clients (e.g. VSCode, Auth Broker) can be abused to blend in. Third-party clients may be malicious or unreviewed - often representing consent grant attacks. Mainly used to identify risky or unexpected app usage.

Target Resource (resource_id / resource_display_name): Defines which MSFT service is being accessed (e.g. MSFT Graph or Teams). High value targets include – Graph API, SharePoint, Outlook, Teams and Directory Services. Resource targeting is often scoped by attacker objectives.

Principal type (user_type): Indicates if the sign-in was by a member (user) or service principal. Phishing campaigns almost always target member accounts. This enables easy filtering in detection logic but helps pair unusual first-party client requests on-behalf-of user principals.

OAuth Grant Type (authentication_processing_details): Key to understanding how the token was obtained – authorization codes, refresh tokens, device codes, client credentials, etc. Whereas refresh tokens and device code reuse are high-fidelity signals of post-compromise.

Geolocation: Enables us to identify atypical sign-ins (e.g. rare country seen) or impossible travel (same user from distant locations in a short time). Combined with session ID and correlation IDs, these can reveal token hijacking, post identity compromise or lateral movement.

Device Metadata (device_detail, trust_type, compliance_state): Includes Device IDs, operating system, trust types, compliance, managed-state and more. Device registration and PRT issuance are tied to this metadata. Often a goal for adversaries to satisfy CAP and gain trusted access that is persistent.

Authentication Protocols and Types (authentication_protocol / incoming_token_type): Reveals whether the session was OAuth-based or if MFA was used. Token sources incoming are those used for this request that provide authN or authZ. Useful for detecting token reuse, non-interactive sign-ins.

Authentication Material and Session Context: Tokens used can be inferred via incoming token type, token protection status and the session ID. Session reuse, long session duration or multiple IPs tied to a single session often indicate abuse.

Conditional Access Policy Status: Evaluated during token issuance – however it heavily influences whether access was granted. This helps identify CAP evasion, unexpected policy outcomes or can factor into risk.

Scopes and Consent Behavior: Requested scopes appear in the SCP or OAuth parameters captured in sign-in logs. Indicators of abuse include offline_access, .default, or broad scopes like Mail.ReadWrite. Consent telemetry can help pivot or correlate if the user approved a suspicious application.

Conclusion

Microsoft Entra ID’s OAuth implementation presents a double-edged sword: it enables powerful, seamless authentication experiences – but also exposes new opportunities for adversaries to exploit trust, session persistence and device registration attack paths.

By replicating the OAuth phishing techniques observed by Volexity, our team was able to validate how attackers abuse legitimate Microsoft applications, token flows, and open-source tools to gain stealthy access to sensitive data. We extended this work through hands-on emulation, diving deep into the mechanics of OAuth phishing and workflows, security token metadata and acquisition, helping surface behavioral indicators that defenders can detect.

Our findings reinforce a key point: OAuth abuse doesn’t rely on malware or code execution. It weaponizes identity, consent, and token reuse – making traditional security controls a challenge – and why log-based detection, correlation and behavioral analysis are so critical.

We hope the emulation artifacts, detection rules, and lessons shared here help defenders across the community better understand – and detect/hunt – this evolving class of cloud-based identity threats.

If you're using Elastic, we’ve open-sourced all the detection rules discussed in this blog to get you started. And if you're hunting in another SIEM, we encourage you to adapt the logic and adjust to your environment accordingly.

Identity is the new perimeter – and it’s time we treated it that way. Stay safe and happy hunting!

Detection Rules

References: