Skip to main content

Cloud VAPT Notes (Azure Initial Access, Recon, Enumeration)

4690 words
Edwin Tok | Shiro
Author
Edwin Tok | Shiro
「 ✦ OwO ✦ 」
Table of Contents

Cloud Pentesting (Azure)
#


Initial Access & Reconnaissance
#

Authentication Methods & Endpoints
#

Portal & Admin URLs:

  • Azure Portal: https://portal.azure.com/
  • Entra ID (Azure AD) Portal: https://entra.microsoft.com/
  • M365 Admin Center: https://admin.microsoft.com/
  • M365 User Portal: https://office.com/
  • Azure DevOps: https://dev.azure.com/
  • Power Platform Admin: https://admin.powerplatform.microsoft.com/

API Endpoints:

  • Microsoft Graph: https://graph.microsoft.com/v1.0/ (Identity, M365, Entra ID)
  • Microsoft Graph Beta: https://graph.microsoft.com/beta/ (Preview features)
  • Azure Resource Manager: https://management.azure.com/ (Azure resources)
  • Azure AD Graph (Deprecated): https://graph.windows.net/ (Being phased out, use MS Graph)

Authentication Tools:
#

Azure CLI (Cross-platform, recommended):#

# Installation (if needed)
# Windows: Download MSI from https://aka.ms/installazurecliwindows
# Linux: curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# macOS: brew install azure-cli

# Interactive browser login
az login

# Device code flow (for headless/restricted environments)
az login --use-device-code

# Service Principal authentication (application credentials)
az login --service-principal \
  --username <application-id> \
  --password <client-secret> \
  --tenant <tenant-id>

# Certificate-based service principal
az login --service-principal \
  --username <application-id> \
  --tenant <tenant-id> \
  --password <path-to-cert-file>

# Managed Identity login (from Azure VM)
az login --identity

# Verify current authentication context
az account show

# List all accessible subscriptions
az account list --all --output table

# Set active subscription
az account set --subscription <subscription-id>

# Get access token (for manual API calls)
az account get-access-token --resource https://management.azure.com/
az account get-access-token --resource https://graph.microsoft.com/

# OPSEC: Authentication events logged in Entra ID Sign-in Logs
# Device code flow provides better anonymity than direct interactive login

Azure PowerShell (Windows-focused but cross-platform):
#

# Installation
Install-Module -Name Az -Repository PSGallery -Force -AllowClobber

# Interactive login
Connect-AzAccount

# Service Principal login
$SecurePassword = ConvertTo-SecureString -String '<client-secret>' -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential('<application-id>', $SecurePassword)
Connect-AzAccount -ServicePrincipal -Tenant '<tenant-id>' -Credential $Credential

# Managed Identity
Connect-AzAccount -Identity

# Access token authentication
$AccessToken = 'eyJ0eXAiOiJKV1QiLCJhbGc...'
Connect-AzAccount -AccessToken $AccessToken -AccountId <subscription-id>

# Verify context
Get-AzContext

# List subscriptions
Get-AzSubscription

# Switch subscription
Set-AzContext -Subscription <subscription-id>

# OPSEC: PowerShell cmdlets generate same audit logs as CLI

Microsoft Graph PowerShell SDK:
#

# Installation (replaces deprecated AzureAD module)
Install-Module Microsoft.Graph -Force -AllowClobber

# Update to latest version
Update-Module Microsoft.Graph

# Connect with specific scopes
Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All", "Application.Read.All", "Group.Read.All"

# Device code flow
Connect-MgGraph -UseDeviceCode -Scopes "User.Read.All"

# Service Principal
$ClientSecret = ConvertTo-SecureString -String '<client-secret>' -AsPlainText -Force
$ClientSecretCredential = New-Object System.Management.Automation.PSCredential('<application-id>', $ClientSecret)
Connect-MgGraph -TenantId '<tenant-id>' -ClientSecretCredential $ClientSecretCredential

# Certificate-based authentication
Connect-MgGraph -ClientId '<application-id>' -TenantId '<tenant-id>' -CertificateThumbprint '<thumbprint>'

# Access token authentication
$AccessToken = 'eyJ0eXAiOiJKV1QiLCJhbGc...'
Connect-MgGraph -AccessToken $AccessToken

# Verify connection
Get-MgContext

# Check current permissions
(Get-MgContext).Scopes

# OPSEC: Graph API calls logged in Entra ID audit logs
# Certificate authentication is more common in enterprises (better for blending in)

Legacy Modules (Deprecated but sometimes present):
#

# AzureAD Module (DEPRECATED - replaced by Microsoft.Graph)
# Install-Module AzureAD -Force
# Connect-AzureAD

# MSOnline Module (DEPRECATED)
# Install-Module MSOnline -Force
# Connect-MsolService

# OPSEC: Avoid using deprecated modules; may indicate old/unmanaged scripts
# However, organizations slow to migrate may still have these in use

Token-Based Authentication (Post-Compromise)
#

Retrieving and Using Access Tokens:
#

# Get Azure Resource Manager (ARM) token
az account get-access-token --resource https://management.azure.com/ --query accessToken --output tsv

# Get Microsoft Graph token
az account get-access-token --resource https://graph.microsoft.com/ --query accessToken --output tsv

# Get Key Vault token
az account get-access-token --resource https://vault.azure.net/ --query accessToken --output tsv

# Get Storage token
az account get-access-token --resource https://storage.azure.com/ --query accessToken --output tsv

# Decode JWT token to inspect claims (use jwt.io or jwt-cli)
echo "eyJ0eXAiOiJKV1QiLCJhbGc..." | base64 -d | jq .

# OPSEC: Tokens have limited lifetime (typically 1 hour)
# Monitor token expiration: check 'exp' claim in JWT

Using Tokens with PowerShell:
#

# Use ARM token
$token = (az account get-access-token --resource https://management.azure.com/ --query accessToken --output tsv)
Connect-AzAccount -AccessToken $token -AccountId <subscription-id>

# Use Graph token with Microsoft Graph PowerShell
$graphToken = (az account get-access-token --resource https://graph.microsoft.com/ --query accessToken --output tsv)
$SecureToken = ConvertTo-SecureString -String $graphToken -AsPlainText -Force
Connect-MgGraph -AccessToken $SecureToken

# Manual API call with token
$Headers = @{
    'Authorization' = "Bearer $token"
    'Content-Type' = 'application/json'
}
Invoke-RestMethod -Uri 'https://management.azure.com/subscriptions?api-version=2020-01-01' -Headers $Headers -Method Get

# OPSEC: Token reuse is common; hard to distinguish from legitimate sessions

Refresh Token Extraction (Advanced):
#

# Refresh tokens stored in various locations:

# Azure CLI token cache (Linux/macOS)
cat ~/.azure/msal_token_cache.json | jq .

# Azure CLI token cache (Windows)
type %USERPROFILE%\.azure\msal_token_cache.json

# PowerShell token cache
# Location varies by version; search for TokenCache.dat

# Extract refresh token and use to generate new access tokens
# (Requires custom tooling or token manipulation libraries)

# OPSEC: Refresh tokens have longer lifetime (90 days default)
# Stealing refresh tokens provides persistent access

Azure AD Entra ID Enumeration
#

Identity Provider & Tenant Discovery:
#

# Check if domain uses Entra ID (Azure AD)
curl "https://login.microsoftonline.com/getuserrealm.srf?login=user@targetdomain.com&xml=1"

# Response indicators:
# NameSpaceType: "Managed" = Entra ID native
# NameSpaceType: "Federated" = Federated with on-prem AD or third-party IdP
# FederationBrandName: Shows federation provider

# Get tenant information without authentication
curl "https://login.microsoftonline.com/targetdomain.com/.well-known/openid-configuration" | jq .

# Extract tenant ID from response
curl "https://login.microsoftonline.com/targetdomain.com/.well-known/openid-configuration" | jq -r .token_endpoint | grep -oP '(?<=/)[\w-]+(?=/oauth2)'

# Alternative tenant discovery
curl "https://login.microsoftonline.com/targetdomain.com/v2.0/.well-known/openid-configuration" | jq .

# Check if tenant allows guest users
# (Enumeration attempt - may be blocked)
curl "https://login.microsoftonline.com/common/GetCredentialType" \
  -H "Content-Type: application/json" \
  -d '{"Username":"test@targetdomain.com"}'

# OPSEC: Unauthenticated queries; minimal logging footprint

User Enumeration:
#

# List all users (requires User.Read.All or Directory.Read.All)
Get-MgUser -All

# Get specific user by UPN
Get-MgUser -UserId "user@domain.com"

# Get user by object ID
Get-MgUser -UserId "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

# Get detailed user properties
Get-MgUser -UserId "user@domain.com" | Select-Object *

# Filter users by criteria
Get-MgUser -Filter "startswith(displayName,'Admin')"
Get-MgUser -Filter "accountEnabled eq true"
Get-MgUser -Filter "userType eq 'Guest'"

# Find privileged users (common naming patterns)
Get-MgUser -All | Where-Object {$_.DisplayName -match "admin|root|service|privileged"}

# Get user's manager
Get-MgUserManager -UserId "user@domain.com"

# Get user's direct reports
Get-MgUserDirectReport -UserId "user@domain.com"

# Get user's group memberships
Get-MgUserMemberOf -UserId "user@domain.com" -All

# Get transitive group memberships (nested groups)
Get-MgUserTransitiveMemberOf -UserId "user@domain.com" -All

# Get user's owned objects (applications, groups, devices)
Get-MgUserOwnedObject -UserId "user@domain.com" -All

# Get user's registered devices
Get-MgUserRegisteredDevice -UserId "user@domain.com" -All

# Get user's owned devices
Get-MgUserOwnedDevice -UserId "user@domain.com" -All

# Check user's sign-in activity (requires AuditLog.Read.All)
Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'user@domain.com'" -Top 50

# Get users with MFA status
Get-MgUser -All | Select-Object DisplayName, UserPrincipalName, Id | ForEach-Object {
    $mfaMethods = Get-MgUserAuthenticationMethod -UserId $_.Id
    [PSCustomObject]@{
        DisplayName = $_.DisplayName
        UPN = $_.UserPrincipalName
        MFAEnabled = ($mfaMethods.Count -gt 1)
        Methods = ($mfaMethods.AdditionalProperties.Keys -join ', ')
    }
}

# OPSEC: User enumeration is normal admin activity; focus on targeted queries
# Avoid bulk exports unless necessary
# Azure CLI user enumeration
az ad user list --output table

# Get specific user
az ad user show --id "user@domain.com"

# Filter users
az ad user list --filter "startswith(displayName,'Admin')" --output table

# Get user's group memberships
az ad user get-member-groups --id "user@domain.com"

# OPSEC: CLI commands generate same Graph API calls as PowerShell

Group Analysis:
#

# List all groups
Get-MgGroup -All

# Filter by group type
Get-MgGroup -Filter "groupTypes/any(c:c eq 'Unified')"  # Microsoft 365 groups
Get-MgGroup -Filter "securityEnabled eq true"  # Security groups

# Find privileged groups
Get-MgGroup -All | Where-Object {$_.DisplayName -match "admin|privileged|global"}

# Get group members
Get-MgGroupMember -GroupId "group-object-id" -All

# Get transitive members (includes nested groups)
Get-MgGroupTransitiveMember -GroupId "group-object-id" -All

# Get group owners
Get-MgGroupOwner -GroupId "group-object-id"

# Find groups user is member of
Get-MgUserMemberOf -UserId "user@domain.com" -All

# Get dynamic group membership rules
Get-MgGroup -GroupId "group-object-id" | Select-Object MembershipRule

# List all dynamic groups (auto-assigned membership)
Get-MgGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')" -All

# OPSEC: Focus on privileged groups first (Global Admins, etc.)
# Azure CLI group enumeration
az ad group list --output table

# Get group members
az ad group member list --group "group-name-or-id"

# Find groups by name pattern
az ad group list --filter "startswith(displayName,'Admin')" --output table

Application & Service Principal Enumeration:
#

# CRITICAL: Applications are high-value targets for privilege escalation

# List all application registrations
Get-MgApplication -All

# Get specific application
Get-MgApplication -ApplicationId "app-object-id"

# Get application by display name
Get-MgApplication -Filter "displayName eq 'MyApp'"

# Get application credentials (NOT secret values, just metadata)
Get-MgApplication -ApplicationId "app-object-id" | Select-Object -ExpandProperty PasswordCredentials
Get-MgApplication -ApplicationId "app-object-id" | Select-Object -ExpandProperty KeyCredentials

# Get application API permissions
Get-MgApplication -ApplicationId "app-object-id" | Select-Object -ExpandProperty RequiredResourceAccess

# Find applications with high-privilege permissions
Get-MgApplication -All | ForEach-Object {
    $perms = $_.RequiredResourceAccess | Where-Object {
        $_.ResourceAccess.Type -eq "Role" -and 
        $_.ResourceAccess.Id -match "RoleManagement|Directory|Application"
    }
    if ($perms) {
        [PSCustomObject]@{
            DisplayName = $_.DisplayName
            AppId = $_.AppId
            Permissions = ($perms.ResourceAccess.Id -join ', ')
        }
    }
}

# Get application owners (privilege escalation target)
Get-MgApplicationOwner -ApplicationId "app-object-id"

# List service principals (enterprise applications)
Get-MgServicePrincipal -All

# Get service principal by application ID
Get-MgServicePrincipal -Filter "appId eq 'application-id'"

# Get service principal permissions
Get-MgServicePrincipal -ServicePrincipalId "sp-object-id" | Select-Object AppRoles, Oauth2PermissionScopes

# Get service principal's assigned app roles
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId "sp-object-id" -All

# Get OAuth2 permission grants (delegated permissions)
Get-MgOauth2PermissionGrant -Filter "clientId eq 'sp-object-id'"

# Find service principals with credentials
Get-MgServicePrincipal -All | Where-Object {
    $spId = $_.Id
    $creds = Get-MgServicePrincipalPasswordCredential -ServicePrincipalId $spId -ErrorAction SilentlyContinue
    $creds.Count -gt 0
}

# OPSEC: Application owners can add credentials to apps
# This is a common privilege escalation path
# Azure CLI application enumeration
az ad app list --output table

# Show specific app
az ad app show --id "application-id"

# List service principals
az ad sp list --all --output table

# Show service principal
az ad sp show --id "service-principal-id"

# Get service principal credentials metadata
az ad sp credential list --id "service-principal-id"

Directory Role Analysis (Entra ID Roles):
#

# List all directory roles
Get-MgDirectoryRole -All

# Get specific role by display name
Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"

# List available role templates (all possible roles)
Get-MgDirectoryRoleTemplate -All

# Get members of a specific role
$globalAdminRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id -All

# Enumerate all privileged roles and their members
$privilegedRoles = @(
    'Global Administrator',
    'Privileged Role Administrator',
    'Security Administrator',
    'Application Administrator',
    'Cloud Application Administrator',
    'User Administrator',
    'Groups Administrator',
    'Exchange Administrator',
    'SharePoint Administrator'
)

foreach ($roleName in $privilegedRoles) {
    $role = Get-MgDirectoryRole -Filter "displayName eq '$roleName'" -ErrorAction SilentlyContinue
    if ($role) {
        $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All
        Write-Host "`n[$roleName] - $($members.Count) members:"
        $members | ForEach-Object { 
            Write-Host "  - $($_.AdditionalProperties.userPrincipalName)"
        }
    }
}

# Get role assignments for specific user
Get-MgUserMemberOf -UserId "user@domain.com" -All | Where-Object {
    $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.directoryRole'
}

# Check Privileged Identity Management (PIM) eligible assignments
# (Requires PrivilegedAccess.Read.AzureAD)
Get-MgRoleManagementDirectoryRoleEligibilitySchedule -All

# Get active PIM assignments
Get-MgRoleManagementDirectoryRoleAssignmentSchedule -All

# OPSEC: Role enumeration is common; focus on privileged roles
# PIM assignments may require activation (time-limited elevation)
# Azure CLI role enumeration
az role definition list --output table

# List directory roles (Entra ID roles)
az rest --method GET --url 'https://graph.microsoft.com/v1.0/directoryRoles'

# Get role members
az rest --method GET --url 'https://graph.microsoft.com/v1.0/directoryRoles/{role-id}/members'

Conditional Access Policy Enumeration:
#

# List all Conditional Access policies (requires Policy.Read.All)
Get-MgIdentityConditionalAccessPolicy -All

# Get specific policy details
Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId "policy-id"

# Find policies targeting specific users/groups
Get-MgIdentityConditionalAccessPolicy -All | Where-Object {
    $_.Conditions.Users.IncludeUsers -contains "user-id" -or
    $_.Conditions.Users.IncludeGroups -contains "group-id"
}

# Find policies with MFA requirements
Get-MgIdentityConditionalAccessPolicy -All | Where-Object {
    $_.GrantControls.BuiltInControls -contains "mfa"
}

# Find disabled policies (potential security gaps)
Get-MgIdentityConditionalAccessPolicy -All | Where-Object {
    $_.State -eq "disabled"
}

# OPSEC: CA policies define access restrictions
# Understanding policies helps identify bypass opportunities

Named Locations (Trusted IPs):
#

# List named locations (trusted IPs, countries)
Get-MgIdentityConditionalAccessNamedLocation -All

# Get IP-based named locations
Get-MgIdentityConditionalAccessNamedLocation -All | Where-Object {
    $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.ipNamedLocation'
}

# Get country-based named locations
Get-MgIdentityConditionalAccessNamedLocation -All | Where-Object {
    $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.countryNamedLocation'
}

# OPSEC: Named locations define trusted IPs
# Operating from these IPs bypasses some CA policies

Authentication Methods & MFA Analysis:
#

# Get authentication methods policy
Get-MgPolicyAuthenticationMethodPolicy

# Get authentication methods for specific user
Get-MgUserAuthenticationMethod -UserId "user@domain.com" -All

# Enumerate users without MFA
Get-MgUser -All | ForEach-Object {
    $methods = Get-MgUserAuthenticationMethod -UserId $_.Id -ErrorAction SilentlyContinue
    if ($methods.Count -le 1) {  # Only password = no MFA
        [PSCustomObject]@{
            DisplayName = $_.DisplayName
            UPN = $_.UserPrincipalName
            MFAEnabled = $false
        }
    }
}

# Get registered devices for user (potential MFA bypass)
Get-MgUserRegisteredDevice -UserId "user@domain.com"

# OPSEC: Users without MFA are easier targets
# Registered devices may allow device-based authentication

Guest User Enumeration:
#

# List all guest users
Get-MgUser -Filter "userType eq 'Guest'" -All

# Get guest user invitation details
Get-MgUser -Filter "userType eq 'Guest'" -All | Select-Object DisplayName, UserPrincipalName, ExternalUserState, CreatedDateTime

# Find recently invited guests
Get-MgUser -Filter "userType eq 'Guest'" -All | Where-Object {
    $_.CreatedDateTime -gt (Get-Date).AddDays(-30)
}

# Get guest users with admin roles
$guestUsers = Get-MgUser -Filter "userType eq 'Guest'" -All
foreach ($guest in $guestUsers) {
    $roles = Get-MgUserMemberOf -UserId $guest.Id | Where-Object {
        $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.directoryRole'
    }
    if ($roles) {
        [PSCustomObject]@{
            DisplayName = $guest.DisplayName
            UPN = $guest.UserPrincipalName
            Roles = ($roles.AdditionalProperties.displayName -join ', ')
        }
    }
}

# OPSEC: Guest users are external accounts with tenant access
# Often overlooked in security reviews; high-value targets

Domain & Tenant Information:
#

# Get organization details
Get-MgOrganization | Select-Object DisplayName, Id, VerifiedDomains, TenantType

# List all verified domains
Get-MgDomain -All

# Get federation settings
Get-MgDomain -DomainId "domain.com" | Select-Object AuthenticationType, IsDefault, IsVerified

# Check if domain is federated
Get-MgDomain -All | Where-Object {$_.AuthenticationType -eq "Federated"}

# Get directory sync status (hybrid environments)
Get-MgOrganization | Select-Object OnPremisesSyncEnabled, OnPremisesLastSyncDateTime

# OPSEC: Federated domains may allow on-prem credential exploitation
# Hybrid environments offer additional attack vectors

Device Enumeration:
#

# List all devices
Get-MgDevice -All

# Get devices by OS
Get-MgDevice -Filter "operatingSystem eq 'Windows'" -All
Get-MgDevice -Filter "operatingSystem eq 'iOS'" -All

# Get compliant devices
Get-MgDevice -Filter "isCompliant eq true" -All

# Get managed devices (Intune enrolled)
Get-MgDevice -Filter "isManaged eq true" -All

# Find stale devices (not logged in recently)
Get-MgDevice -All | Where-Object {
    $_.ApproximateLastSignInDateTime -lt (Get-Date).AddDays(-90)
}

# Get registered owners of device
Get-MgDeviceRegisteredOwner -DeviceId "device-id"

# OPSEC: Stale devices may have weaker security postures
# Device compliance affects Conditional Access policies

License & Subscription Analysis:
#

# Get organization subscribed SKUs
Get-MgSubscribedSku -All

# Get users with specific license
$e5Sku = Get-MgSubscribedSku -All | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPREMIUM"}
Get-MgUser -All | Where-Object {
    $_.AssignedLicenses.SkuId -contains $e5Sku.SkuId
}

# Find users with privileged licenses (E5, P2, etc.)
Get-MgUser -All | Where-Object {
    $licenseIds = $_.AssignedLicenses.SkuId
    $skus = Get-MgSubscribedSku -All | Where-Object {$licenseIds -contains $_.SkuId}
    $skus.SkuPartNumber -match "E5|P2|PREMIUM"
} | Select-Object DisplayName, UserPrincipalName

# OPSEC: License assignments indicate feature availability
# E5 licenses enable advanced security features (PIM, CA, etc.)

Audit Log Analysis (Requires AuditLog.Read.All):
#

# Get recent sign-in logs
Get-MgAuditLogSignIn -Top 100

# Filter sign-ins by user
Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'user@domain.com'" -Top 50

# Find failed sign-in attempts
Get-MgAuditLogSignIn -Filter "status/errorCode ne 0" -Top 100

# Find sign-ins from risky locations
Get-MgAuditLogSignIn -Filter "riskState eq 'atRisk'" -Top 50

# Get directory audit logs
Get-MgAuditLogDirectoryAudit -Top 100

# Filter audit logs by activity
Get-MgAuditLogDirectoryAudit -Filter "activityDisplayName eq 'Add member to role'" -Top 50

# Find recent privilege escalations
Get-MgAuditLogDirectoryAudit -Filter "category eq 'RoleManagement'" -Top 100

# OPSEC: Audit logs show defender's visibility
# Understanding logged events helps plan evasion

Azure Resource Manager (ARM) Enumeration
#

Subscription Discovery & Context:
#

# List all accessible subscriptions
Get-AzSubscription -WarningAction SilentlyContinue

# Get current subscription context
Get-AzContext

# Get detailed subscription information
Get-AzSubscription | Select-Object Name, Id, TenantId, State, SubscriptionPolicies

# Set active subscription
Set-AzContext -Subscription "subscription-id"

# Check subscription quota and usage
Get-AzVMUsage -Location "eastus"

# Get subscription resource providers
Get-AzResourceProvider -ListAvailable | Select-Object ProviderNamespace, RegistrationState

# OPSEC: Subscription enumeration is routine admin activity
# Azure CLI subscription enumeration
az account list --all --output table

# Show current subscription
az account show

# Set active subscription
az account set --subscription "subscription-id"

# List enabled resource providers
az provider list --query "[?registrationState=='Registered']" --output table

Resource Group Enumeration:
#

# List all resource groups
Get-AzResourceGroup

# Get resource groups in specific location
Get-AzResourceGroup | Where-Object {$_.Location -eq "eastus"}

# Get resource group with tags
Get-AzResourceGroup | Where-Object {$_.Tags.Keys -contains "Environment"}

# Get resources within resource group
Get-AzResource -ResourceGroupName "rg-name"

# Count resources by resource group
Get-AzResourceGroup | ForEach-Object {
    $rgName = $_.ResourceGroupName
    $resourceCount = (Get-AzResource -ResourceGroupName $rgName).Count
    [PSCustomObject]@{
        ResourceGroup = $rgName
        Location = $_.Location
        ResourceCount = $resourceCount
    }
} | Sort-Object -Property ResourceCount -Descending

# OPSEC: Focus on resource groups with production-sounding names
# Tags often indicate environment (prod, dev, test)
# Azure CLI resource group enumeration
az group list --output table

# List resources in resource group
az resource list --resource-group "rg-name" --output table

# Get resource group details
az group show --name "rg-name"

Resource Enumeration (All Types):
#

# List all resources across subscription
Get-AzResource | Select-Object Name, ResourceType, ResourceGroupName, Location

# Filter by resource type
Get-AzResource -ResourceType "Microsoft.Compute/virtualMachines"
Get-AzResource -ResourceType "Microsoft.Storage/storageAccounts"
Get-AzResource -ResourceType "Microsoft.KeyVault/vaults"

# Search resources by name pattern
Get-AzResource | Where-Object {$_.Name -match "prod|production"}

# Get resources with specific tags
Get-AzResource -TagName "Environment" -TagValue "Production"

# Export resource inventory
Get-AzResource | Export-Csv -Path "azure-resources.csv" -NoTypeInformation

# OPSEC: Resource listing is normal; avoid bulk exports if possible
# Azure CLI comprehensive resource listing
az resource list --output table

# Filter by resource type
az resource list --resource-type "Microsoft.Compute/virtualMachines" --output table

# Query with JMESPath
az resource list --query "[?type=='Microsoft.Storage/storageAccounts'].{Name:name,Location:location}" --output table

RBAC (Role-Based Access Control) Analysis:
#

# List all role assignments in subscription
Get-AzRoleAssignment

# Get role assignments for specific user
Get-AzRoleAssignment -SignInName "user@domain.com"

# Get role assignments for service principal
Get-AzRoleAssignment -ServicePrincipalName "app-id"

# Get role assignments at specific scope
Get-AzRoleAssignment -Scope "/subscriptions/subscription-id/resourceGroups/rg-name"

# Find users with Owner role
Get-AzRoleAssignment | Where-Object {$_.RoleDefinitionName -eq "Owner"}

# Find users with Contributor role
Get-AzRoleAssignment | Where-Object {$_.RoleDefinitionName -eq "Contributor"}

# Get custom role definitions
Get-AzRoleDefinition | Where-Object {$_.IsCustom -eq $true}

# Get role definition details (permissions)
Get-AzRoleDefinition -Name "Contributor" | Select-Object -ExpandProperty Permissions

# Find high-privilege custom roles
Get-AzRoleDefinition | Where-Object {
    $_.IsCustom -eq $true -and 
    ($_.Actions -contains "*" -or $_.Actions -match "Microsoft.Authorization/*/write")
}

# Enumerate role assignments by scope level
# Subscription level (highest privilege)
Get-AzRoleAssignment | Where-Object {
    $_.Scope -match "^/subscriptions/[^/]+$"
}

# Resource group level
Get-AzRoleAssignment | Where-Object {
    $_.Scope -match "^/subscriptions/[^/]+/resourceGroups/"
}

# Check for "User Access Administrator" role (can grant roles)
Get-AzRoleAssignment | Where-Object {
    $_.RoleDefinitionName -eq "User Access Administrator"
}

# OPSEC: RBAC assignments define authorization boundaries
# Owner and User Access Administrator are privilege escalation paths
# Azure CLI RBAC enumeration
az role assignment list --all --output table

# Get assignments for specific user
az role assignment list --assignee "user@domain.com" --all --output table

# List custom roles
az role definition list --custom-role-only --output table

# Get role definition
az role definition list --name "Contributor"

# Find Owner assignments
az role assignment list --role "Owner" --all --output table

Virtual Machine Enumeration:
#

# List all VMs
Get-AzVM

# Get VMs with detailed information
Get-AzVM -Status | Select-Object Name, ResourceGroupName, Location, PowerState, @{N="OsType";E={$_.StorageProfile.OsDisk.OsType}}

# Find running VMs
Get-AzVM -Status | Where-Object {$_.PowerState -eq "VM running"}

# Get VM with network interfaces
Get-AzVM | ForEach-Object {
    $vm = $_
    $nic = Get-AzNetworkInterface -ResourceId $vm.NetworkProfile.NetworkInterfaces[0].Id
    $publicIp = if ($nic.IpConfigurations[0].PublicIpAddress) {
        Get-AzPublicIpAddress -ResourceId $nic.IpConfigurations[0].PublicIpAddress.Id
    }
    [PSCustomObject]@{
        VMName = $vm.Name
        ResourceGroup = $vm.ResourceGroupName
        PrivateIP = $nic.IpConfigurations[0].PrivateIpAddress
        PublicIP = $publicIp.IpAddress
        Location = $vm.Location
        PowerState = ($vm | Get-AzVM -Status).PowerState
    }
}

# Get VMs with managed identities
Get-AzVM | Where-Object {$_.Identity -ne $null} | Select-Object Name, @{N="IdentityType";E={$_.Identity.Type}}

# Get VM extensions (may contain configurations/secrets)
Get-AzVM | ForEach-Object {
    Get-AzVMExtension -ResourceGroupName $_.ResourceGroupName -VMName $_.Name -ErrorAction SilentlyContinue
}

# Get VM boot diagnostics (may contain error logs)
Get-AzVM | ForEach-Object {
    Get-AzVMBootDiagnosticsData -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Windows -ErrorAction SilentlyContinue
}

# OPSEC: VMs with managed identities can access Azure resources
# Public IPs are potential entry points
# Azure CLI VM enumeration
az vm list --output table

# Get VMs with public IPs
az vm list-ip-addresses --output table

# Show VM details
az vm show --resource-group "rg-name" --name "vm-name"

# Get VM run-command capability (remote execution)
az vm run-command list --location "eastus"

Storage Account Enumeration:
#

# List all storage accounts
Get-AzStorageAccount

# Get storage account with access keys
Get-AzStorageAccount | ForEach-Object {
    $keys = Get-AzStorageAccountKey -ResourceGroupName $_.ResourceGroupName -Name $_.StorageAccountName
    [PSCustomObject]@{
        Name = $_.StorageAccountName
        ResourceGroup = $_.ResourceGroupName
        Location = $_.Location
        PrimaryKey = $keys[0].Value
        SecondaryKey = $keys[1].Value
        EnableHttpsTrafficOnly = $_.EnableHttpsTrafficOnly
        AllowBlobPublicAccess = $_.AllowBlobPublicAccess
    }
}

# List blob containers in storage account
$storageContext = New-AzStorageContext -StorageAccountName "storage-name" -StorageAccountKey "key"
Get-AzStorageContainer -Context $storageContext

# List blobs in container
Get-AzStorageBlob -Container "container-name" -Context $storageContext

# Download blob
Get-AzStorageBlobContent -Container "container-name" -Blob "blob-name" -Destination "./downloaded-blob" -Context $storageContext

# Check for anonymous access
Get-AzStorageContainer -Context $storageContext | Where-Object {$_.PublicAccess -ne "Off"}

# List file shares
Get-AzStorageShare -Context $storageContext

# Get storage account network rules
Get-AzStorageAccount | ForEach-Object {
    Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $_.ResourceGroupName -Name $_.StorageAccountName
}

# Find storage accounts with public access enabled
Get-AzStorageAccount | Where-Object {$_.AllowBlobPublicAccess -eq $true}

# OPSEC: Storage accounts often contain sensitive data
# Access keys provide full control; look for publicly accessible containers
# Azure CLI storage enumeration
az storage account list --output table

# Get storage account keys
az storage account keys list --account-name "storage-name"

# List containers (requires account key or SAS)
az storage container list --account-name "storage-name" --account-key "key"

# List blobs
az storage blob list --container-name "container" --account-name "storage-name" --account-key "key"

# Download blob
az storage blob download --container-name "container" --name "blob-name" --file "./local-file" --account-name "storage-name" --account-key "key"

# Check for public access
az storage container show-permission --name "container" --account-name "storage-name"

Key Vault Enumeration:
#

# List all key vaults
Get-AzKeyVault

# Get key vault access policies
Get-AzKeyVault | ForEach-Object {
    $vault = $_
    $policies = $vault.AccessPolicies
    [PSCustomObject]@{
        VaultName = $vault.VaultName
        ResourceGroup = $vault.ResourceGroupName
        Location = $vault.Location
        EnabledForDeployment = $vault.EnabledForDeployment
        EnabledForDiskEncryption = $vault.EnabledForDiskEncryption
        PoliciesCount = $policies.Count
    }
}

# List secrets in key vault (requires permissions)
Get-AzKeyVaultSecret -VaultName "vault-name" | Select-Object Name, Created, Updated, Enabled

# Get secret value
Get-AzKeyVaultSecret -VaultName "vault-name" -Name "secret-name" -AsPlainText

# List keys
Get-AzKeyVaultKey -VaultName "vault-name"

# List certificates
Get-AzKeyVaultCertificate -VaultName "vault-name"

# Get key vault network settings
Get-AzKeyVault -VaultName "vault-name" | Select-Object NetworkAcls

# Find key vaults with disabled firewall
Get-AzKeyVault | Where-Object {
    $_.NetworkAcls.DefaultAction -eq "Allow"
}

# Check for soft-delete and purge protection
Get-AzKeyVault | Select-Object VaultName, EnableSoftDelete, EnablePurgeProtection

# OPSEC: Key Vaults store sensitive secrets, keys, certificates
# Access is heavily logged; target specific secrets if possible
# Azure CLI Key Vault enumeration
az keyvault list --output table

# List secrets
az keyvault secret list --vault-name "vault-name"

# Get secret value
az keyvault secret show --vault-name "vault-name" --name "secret-name" --query value --output tsv

# List keys
az keyvault key list --vault-name "vault-name"

# List certificates
az keyvault certificate list --vault-name "vault-name"

# Show key vault properties
az keyvault show --name "vault-name"

SQL Database Enumeration:
#

# List SQL servers
Get-AzSqlServer

# Get SQL server firewall rules
Get-AzSqlServer | ForEach-Object {
    Get-AzSqlServerFirewallRule -ResourceGroupName $_.ResourceGroupName -ServerName $_.ServerName
}

# Find servers with public access (0.0.0.0 rule)
Get-AzSqlServer | ForEach-Object {
    $rules = Get-AzSqlServerFirewallRule -ResourceGroupName $_.ResourceGroupName -ServerName $_.ServerName
    if ($rules | Where-Object {$_.StartIpAddress -eq "0.0.0.0" -and $_.EndIpAddress -eq "255.255.255.255"}) {
        [PSCustomObject]@{
            ServerName = $_.ServerName
            ResourceGroup = $_.ResourceGroupName
            PublicAccess = $true
        }
    }
}

# List databases on SQL server
Get-AzSqlServer | ForEach-Object {
    Get-AzSqlDatabase -ResourceGroupName $_.ResourceGroupName -ServerName $_.ServerName
}

# Get SQL server administrators
Get-AzSqlServer | ForEach-Object {
    Get-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName $_.ResourceGroupName -ServerName $_.ServerName
}

# Check for Entra ID (Azure AD) authentication
Get-AzSqlServer | Select-Object ServerName, @{N="EntraIDAdmin";E={
    (Get-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName $_.ResourceGroupName -ServerName $_.ServerName).DisplayName
}}

# OPSEC: SQL servers with public access and weak firewall rules are targets
# Entra ID authentication may allow token-based access
# Azure CLI SQL enumeration
az sql server list --output table

# List databases
az sql db list --server "server-name" --resource-group "rg-name"

# Show firewall rules
az sql server firewall-rule list --server "server-name" --resource-group "rg-name"

# Get server details
az sql server show --name "server-name" --resource-group "rg-name"

Web App & App Service Enumeration:
#

# List all web apps
Get-AzWebApp

# Get web app with configuration
Get-AzWebApp | ForEach-Object {
    $config = Get-AzWebAppSlot -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Slot production -ErrorAction SilentlyContinue
    [PSCustomObject]@{
        Name = $_.Name
        ResourceGroup = $_.ResourceGroupName
        DefaultHostName = $_.DefaultHostName
        State = $_.State
        HttpsOnly = $_.HttpsOnly
        ManagedIdentity = ($_.Identity -ne $null)
    }
}

# Get web app application settings (may contain secrets)
Get-AzWebApp | ForEach-Object {
    $settings = Get-AzWebAppSlot -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Slot production | Select-Object -ExpandProperty SiteConfig
    [PSCustomObject]@{
        AppName = $_.Name
        AppSettings = $settings.AppSettings
        ConnectionStrings = $settings.ConnectionStrings
    }
}

# Download web app configuration
Get-AzWebApp | ForEach-Object {
    $xml = [xml](Get-AzWebAppPublishingProfile -ResourceGroupName $_.ResourceGroupName -Name $_.Name)
    $xml.publishData.publishProfile | Where-Object {$_.publishMethod -eq "MSDeploy"}
}

# Get deployment credentials (if accessible)
Get-AzWebApp | ForEach-Object {
    Get-AzWebAppPublishingProfile -ResourceGroupName $_.ResourceGroupName -Name $_.Name -OutputFile "$($_.Name)-profile.xml"
}

# Check for source control configuration
Get-AzWebApp | ForEach-Object {
    Get-AzWebAppSourceControl -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction SilentlyContinue
}

# OPSEC: Web apps often have connection strings, API keys in settings
# Publishing profiles contain deployment credentials
# Azure CLI web app enumeration
az webapp list --output table

# Show web app configuration
az webapp config show --name "webapp-name" --resource-group "rg-name"

# Get app settings
az webapp config appsettings list --name "webapp-name" --resource-group "rg-name"

# Get connection strings
az webapp config connection-string list --name "webapp-name" --resource-group "rg-name"

# Download publishing profile
az webapp deployment list-publishing-profiles --name "webapp-name" --resource-group "rg-name"

Function App Enumeration:
#

# List all function apps
Get-AzFunctionApp

# Get function app configuration
Get-AzFunctionApp | ForEach-Object {
    $settings = Get-AzFunctionAppSetting -ResourceGroupName $_.ResourceGroupName -Name $_.Name
    [PSCustomObject]@{
        Name = $_.Name
        ResourceGroup = $_.ResourceGroupName
        Runtime = $_.Runtime
        ApplicationSettings = $settings
    }
}

# List functions within function app
Get-AzFunctionApp | ForEach-Object {
    $functions = Get-AzFunctionAppFunction -ResourceGroupName $_.ResourceGroupName -Name $_.Name
    foreach ($function in $functions) {
        [PSCustomObject]@{
            AppName = $_.Name
            FunctionName = $function.Name
            Trigger = $function.Config.Bindings | Where-Object {$_.Type -match "Trigger"}
        }
    }
}

# OPSEC: Function apps similar to web apps; check for secrets in settings

Container & Kubernetes Enumeration:
#

# List container registries (ACR)
Get-AzContainerRegistry

# Get ACR credentials
Get-AzContainerRegistry | ForEach-Object {
    Get-AzContainerRegistryCredential -ResourceGroupName $_.ResourceGroupName -Name $_.Name
}

# List AKS clusters
Get-AzAksCluster

# Get AKS cluster credentials (kubeconfig)
Get-AzAksCluster | ForEach-Object {
    Import-AzAksCredential -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Force
}

# After importing kubeconfig, use kubectl
# kubectl get nodes
# kubectl get pods --all-namespaces
# kubectl get secrets --all-namespaces

# List container instances (ACI)
Get-AzContainerGroup

# OPSEC: Container registries may contain images with secrets
# AKS clusters provide access to Kubernetes APIs
# Azure CLI container enumeration
az acr list --output table

# Get ACR credentials
az acr credential show --name "acr-name"

# List AKS clusters
az aks list --output table

# Get AKS credentials
az aks get-credentials --resource-group "rg-name" --name "aks-name"

# List container instances
az container list --output table

Network Security Group (NSG) Analysis:
#

# List all NSGs
Get-AzNetworkSecurityGroup

# Get NSG rules
Get-AzNetworkSecurityGroup | ForEach-Object {
    $nsg = $_
    $rules = $nsg.SecurityRules
    foreach ($rule in $rules) {
        [PSCustomObject]@{
            NSGName = $nsg.Name
            RuleName = $rule.Name
            Priority = $rule.Priority
            Direction = $rule.Direction
            Access = $rule.Access
            Protocol = $rule.Protocol
            SourceAddress = ($rule.SourceAddressPrefix -join ', ')
            SourcePort = ($rule.SourcePortRange -join ', ')
            DestinationAddress = ($rule.DestinationAddressPrefix -join ', ')
            DestinationPort = ($rule.DestinationPortRange -join ', ')
        }
    }
}

# Find overly permissive inbound rules
Get-AzNetworkSecurityGroup | ForEach-Object {
    $_.SecurityRules | Where-Object {
        $_.Direction -eq "Inbound" -and 
        $_.Access -eq "Allow" -and
        ($_.SourceAddressPrefix -contains "*" -or $_.SourceAddressPrefix -contains "0.0.0.0/0" -or $_.SourceAddressPrefix -contains "Internet")
    }
}

# OPSEC: NSGs define network access rules
# Overly permissive rules indicate potential entry points

Virtual Network Enumeration:
#

# List all virtual networks
Get-AzVirtualNetwork

# Get VNet with subnets
Get-AzVirtualNetwork | ForEach-Object {
    $vnet = $_
    foreach ($subnet in $vnet.Subnets) {
        [PSCustomObject]@{
            VNetName = $vnet.Name
            SubnetName = $subnet.Name
            AddressPrefix = $subnet.AddressPrefix
            NSG = $subnet.NetworkSecurityGroup.Id
        }
    }
}

# List VPN gateways
Get-AzVirtualNetworkGateway

# List ExpressRoute circuits
Get-AzExpressRouteCircuit

# OPSEC: Network topology reveals connectivity and potential pivot points

Automation Account Enumeration:
#

# List automation accounts
Get-AzAutomationAccount

# List runbooks
Get-AzAutomationAccount | ForEach-Object {
    Get-AzAutomationRunbook -ResourceGroupName $_.ResourceGroupName -AutomationAccountName $_.AutomationAccountName
}

# Get runbook content
Get-AzAutomationRunbook -ResourceGroupName "rg-name" -AutomationAccountName "account-name" -Name "runbook-name" | Export-AzAutomationRunbook -OutputFolder "./runbooks/"

# List automation variables (may contain credentials)
Get-AzAutomationAccount | ForEach-Object {
    Get-AzAutomationVariable -ResourceGroupName $_.ResourceGroupName -AutomationAccountName $_.AutomationAccountName
}

# Get variable values
Get-AzAutomationVariable -ResourceGroupName "rg-name" -AutomationAccountName "account-name" -Name "variable-name" | Select-Object -ExpandProperty Value

# List credentials in automation account
Get-AzAutomationAccount | ForEach-Object {
    Get-AzAutomationCredential -ResourceGroupName $_.ResourceGroupName -AutomationAccountName $_.AutomationAccountName
}

# OPSEC: Automation accounts often run with elevated privileges
# Runbooks may contain hardcoded secrets or access tokens