Azure Security Assessment
Concepts & reference: See Azure for the Entra ID model, RBAC scope hierarchy, and key services. Tooling: See Cloud Audit Tools for ScoutSuite, Prowler, CloudFox setup and usage.
1. Setup & Access
az login
az account list --output table
az account set --subscription "<Subscription Name or ID>"
az ad signed-in-user show- Portal manual checks: https://portal.azure.com — Home > Advisor > Security > Recommendations
- CIS Benchmark policy: Home > Policy > Assign CIS compliance policy initiative
2. Entra ID (Azure AD) & IAM / RBAC Review
# Users and groups
az ad user list
az ad group list
az ad group member list --group <GroupNameOrId>
# Role assignments
az role assignment list --all
az role assignment list --all --query '[?roleDefinitionName==`Owner`]'
# Custom role definitions
az role definition list --custom-role-only true
# Service Principals & App Registrations
az ad sp list --all
az ad app list --all
# App Registration credentials (expiring secrets / certificates)
az ad app credential list --id <AppId>
# Managed Identities
az identity listWhat to look for:
- Over-privileged roles:
Owner,User Access Administratorassigned at subscription scope - Custom roles with wildcard
Actions: ["*"] - App Registrations with client secrets that never expire or are expiring
- Service Principals with Contributor or higher permissions
- Managed identities assigned to VMs or services — what can they access?
Conditional Access
# Requires Microsoft Graph permissions
az rest --method GET --url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"What to look for:
- MFA not enforced for all users (especially admins)
- Policies with broad exclusions (guest users, break-glass accounts not monitored)
- No policy blocking legacy authentication protocols
Privileged Identity Management (PIM)
Check via Portal: Entra ID > Privileged Identity Management > Azure AD Roles / Azure Resources
What to look for:
- Permanent admin assignments instead of time-bound eligible assignments
- No approval workflow for privileged role activation
- No MFA requirement on PIM activation
3. Monitoring & Logging
# Diagnostic settings (per resource)
az monitor diagnostic-settings list --resource <ResourceID>
# Activity logs (last 7 days)
az monitor activity-log list --offset 7d
# Azure Policy assignments
az policy assignment listWhat to look for:
- Microsoft Defender for Cloud enabled for all subscriptions (check in Portal: Defender for Cloud > Environment Settings)
- Diagnostic settings not configured on key resources (Key Vaults, Storage Accounts)
- Activity log retention below 90 days
- Azure Policy compliance status — non-compliant resources
4. Storage Accounts
# List all storage accounts
az storage account list --output table
# Per-account checks
az storage account show --name <StorageAccount>
az storage account show --name <StorageAccount> --query '[allowBlobPublicAccess, enableHttpsTrafficOnly, minimumTlsVersion]'
# List containers (check for public access)
az storage container list --account-name <StorageAccount> --auth-mode loginWhat to look for:
allowBlobPublicAccess: true— individual containers may be publicly accessibleenableHttpsTrafficOnly: false— allows unencrypted HTTP access- Soft delete not enabled (no recovery from accidental deletion)
- Storage accounts accessible from all networks (no firewall restriction)
- SAS tokens with overly broad permissions or no expiry
5. Networking
# Virtual networks
az network vnet list
# Network Security Groups
az network nsg list
az network nsg rule list --nsg-name <NSGName> --resource-group <RG>
# Public IPs
az network public-ip list
# Private endpoints
az network private-endpoint listWhat to look for:
- NSG rules allowing
0.0.0.0/0inbound on SSH (22), RDP (3389), or WinRM (5985/5986) - VNet peering connections — traffic flow and whether NSGs restrict it appropriately
- Resources with public IPs that should be internal-only
- Missing private endpoints for PaaS services (Key Vault, Storage, SQL)
6. Compute (VMs & Functions)
# Virtual Machines
az vm list --show-details --output table
# Check disk encryption
az vm encryption show --name <VMName> --resource-group <RG>
# Azure Functions
az functionapp list --output table
az functionapp config appsettings list --name <FunctionApp> --resource-group <RG>
# Review app settings for plaintext secrets/connection strings
# Azure SQL
az sql server list
az sql server firewall-rule list --server <ServerName> --resource-group <RG>
az sql db list --server <ServerName> --resource-group <RG>What to look for:
- VMs without disk encryption (Azure Disk Encryption or SSE with CMK)
- VMs with public IPs and no NSG restricting inbound
- Managed identities on VMs — what role assignments do they have?
- Function App settings containing plaintext connection strings or API keys
- SQL Server firewall rules allowing
0.0.0.0 - 255.255.255.255(“Allow Azure services” is often too broad) - SQL databases without Transparent Data Encryption (TDE) or auditing enabled
7. Secrets & Key Management
# Key Vaults
az keyvault list
az keyvault show --name <VaultName>
# List secrets (names only — requires Key Vault access)
az keyvault secret list --vault-name <VaultName>
# Key Vault access policies vs RBAC
az keyvault show --name <VaultName> --query '[properties.accessPolicies, properties.enableRbacAuthorization]'What to look for:
- Key Vault firewall not enabled (accessible from all networks)
- Soft delete and purge protection not enabled
- Access policies granting broad permissions (
*on secrets/keys) to service principals - Key Vault not using RBAC mode (access policies are less granular)
- Secrets without expiry dates
8. Automated Tools
See Cloud Audit Tools for ScoutSuite, Prowler, and CloudFox setup and usage.
Recommended workflow for a standard Azure audit:
- Prowler — CIS Azure benchmark run, structured findings for the report
- ScoutSuite — visual inventory across subscription, catches misconfigs Prowler may miss
- CloudFox — follow up on RBAC/managed identity findings for privilege escalation paths
9. Reporting & Output
# Export as JSON
az <service> <command> --output json > results.json
# Filter with jq
jq '.[] | {name, id}' results.json
jq '.[] | select(.roleDefinitionName == "Owner") | {principalName, scope}' role-assignments.json