After running security assessments on many Azure environments, patterns emerge. Here are the most common issues we find and how to address them.
Storage Account Issues
Public Blob Access Enabled
Risk: Containers can be accidentally made public, exposing data.
Check:
Get-AzStorageAccount | ForEach-Object {
[PSCustomObject]@{
Name = $_.StorageAccountName
AllowBlobPublicAccess = $_.AllowBlobPublicAccess
}
}
Fix:
resource "azurerm_storage_account" "this" {
allow_nested_items_to_be_public = false
}
No Soft Delete
Risk: Accidental deletion is permanent.
Check:
$ctx = (Get-AzStorageAccount -ResourceGroupName "rg" -Name "storage").Context
Get-AzStorageBlobServiceProperty -StorageAccount $ctx |
Select-Object DeleteRetentionPolicy
Fix: Enable 30-day soft delete for blobs and containers.
HTTP Traffic Allowed
Risk: Data can be intercepted in transit.
Check: Look for enable_https_traffic_only = false or missing.
Fix: Always set https_traffic_only_enabled = true.
Key Vault Issues
No Purge Protection
Risk: Deleted secrets are gone forever; ransomware can destroy your keys.
Check:
Get-AzKeyVault | Select-Object VaultName, EnablePurgeProtection
Fix:
resource "azurerm_key_vault" "this" {
purge_protection_enabled = true
soft_delete_retention_days = 90
}
Public Network Access
Risk: Secrets accessible from internet.
Check: Look for key vaults without network rules.
Fix: Use private endpoints or IP-based access rules.
SQL Server Issues
Public Endpoint Enabled
Risk: Database accessible from internet (even with firewall).
Check:
Get-AzSqlServer | ForEach-Object {
$rules = Get-AzSqlServerFirewallRule -ServerName $_.ServerName -ResourceGroupName $_.ResourceGroupName
[PSCustomObject]@{
Server = $_.ServerName
AllowAzureIPs = ($rules | Where-Object { $_.FirewallRuleName -eq "AllowAllWindowsAzureIps" }) -ne $null
PublicRules = ($rules | Where-Object { $_.StartIpAddress -ne "0.0.0.0" }).Count
}
}
Fix: Use private endpoints and disable public access.
No Azure AD Admin
Risk: Only SQL authentication available.
Check:
Get-AzSqlServerActiveDirectoryAdministrator -ServerName "sql-server" -ResourceGroupName "rg"
Fix: Set an Azure AD admin and use Azure AD authentication.
App Service Issues
HTTPS Not Enforced
Risk: Users can access over unencrypted HTTP.
Check:
Get-AzWebApp | Select-Object Name, HttpsOnly
Fix:
resource "azurerm_linux_web_app" "this" {
https_only = true
}
TLS 1.0/1.1 Enabled
Risk: Weak encryption protocols.
Check:
Get-AzWebApp | ForEach-Object {
$config = Get-AzWebAppConfiguration -ResourceGroupName $_.ResourceGroup -Name $_.Name
[PSCustomObject]@{
Name = $_.Name
MinTlsVersion = $config.MinTlsVersion
}
}
Fix: Set minimum TLS to 1.2.
FTP Enabled
Risk: Insecure deployment method.
Check: Look for ftps_state = "AllAllowed".
Fix: Set ftps_state = "Disabled" or "FtpsOnly".
Virtual Machine Issues
No Disk Encryption
Risk: Data at rest unprotected.
Check:
Get-AzVM | ForEach-Object {
$status = Get-AzVmDiskEncryptionStatus -ResourceGroupName $_.ResourceGroupName -VMName $_.Name
[PSCustomObject]@{
VM = $_.Name
OsDiskEncrypted = $status.OsVolumeEncrypted
DataDisksEncrypted = $status.DataVolumesEncrypted
}
}
Fix: Enable Azure Disk Encryption or use platform-managed encryption.
Public IP Attached
Risk: Direct internet exposure.
Check:
Get-AzVM | ForEach-Object {
$nic = Get-AzNetworkInterface -ResourceId $_.NetworkProfile.NetworkInterfaces[0].Id
$pip = $nic.IpConfigurations[0].PublicIpAddress
[PSCustomObject]@{
VM = $_.Name
HasPublicIP = $pip -ne $null
}
}
Fix: Use Azure Bastion, VPN, or Azure Firewall instead.
Network Issues
NSG Flow Logs Disabled
Risk: No visibility into network traffic.
Check:
Get-AzNetworkWatcher | Get-AzNetworkWatcherFlowLog
Fix: Enable NSG flow logs to Storage or Log Analytics.
Overly Permissive NSG Rules
Risk: Unnecessary network exposure.
Check: Look for rules with:
- Source: Any/0.0.0.0/0
- Port range: * or large ranges
Fix: Restrict to specific IPs and ports.
Identity Issues
No Conditional Access
Risk: Access from any device/location.
Check: Review Conditional Access policies in Entra admin center.
Fix: Implement basic policies:
- Block legacy authentication
- Require MFA for admins
- Require compliant devices for corporate apps
Global Admins Without MFA
Risk: Complete tenant compromise from password theft.
Check:
Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'" |
Get-MgDirectoryRoleMember
Fix: Enforce MFA for all admin roles.
Quick Assessment Script
# Storage accounts
Get-AzStorageAccount | Where-Object { $_.AllowBlobPublicAccess -eq $true } |
Select-Object StorageAccountName
# Key Vaults without purge protection
Get-AzKeyVault | Where-Object { $_.EnablePurgeProtection -ne $true } |
Select-Object VaultName
# App Services without HTTPS
Get-AzWebApp | Where-Object { $_.HttpsOnly -ne $true } |
Select-Object Name
# VMs with public IPs
Get-AzPublicIpAddress | Where-Object { $_.IpConfiguration -ne $null } |
Select-Object Name, IpAddress
Need a security assessment of your Azure environment? Get in touch - we help organisations identify and fix security gaps.