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