Azure Network Security Group default ‘AllowVnetInBound’ rule combined with flat VNet peering exposing lateral movement
01 Incident diagnostics
- The Symptom
- Security review or a live incident shows a compromised VM in a low-trust spoke (e.g. a shared dev/test VNet) successfully completing TCP connections into production database or app-tier subnets on ports that were never explicitly allowlisted, despite each subnet having its own NSG attached.
- Detection
- Run az network watcher show-security-group-view --resource-group --vm and inspect effectiveSecurityRules for AllowVnetInBound with sourceAddressPrefix listing multiple peered CIDRs. Cross-check NSG Flow Logs via Traffic Analytics with a KQL filter on NSGFlowLog_CL | where NSGRule_s == "AllowVnetInBound" and FlowStatus_s == "A" to enumerate which peered VNet ranges are actually traversing the default allow.
- Root Cause
- The built-in AllowVnetInBound rule matches the VirtualNetwork service tag, which Azure dynamically expands to include the local VNet's address space plus every VNet address space reachable via active peerings, including AllowForwardedTraffic and UseRemoteGateways settings. In topologies using direct spoke-to-spoke mesh peering rather than hub-spoke with a forced-tunnel NVA, this tag silently aggregates every peered spoke's CIDR, so an NSG containing only default rules provides zero segmentation between tiers that were assumed to be isolated by separate VNets.
- Prevention
- Replace direct spoke-to-spoke mesh peering with hub-spoke topology forcing all inter-spoke traffic through Azure Firewall via UDR 0.0.0.0/0, and deploy Azure Virtual Network Manager security admin rules to enforce a global deny on VirtualNetwork-tagged inbound traffic that NSG owners cannot override. Alert on new AllowVnetInBound flow log entries crossing tier boundaries using a scheduled Log Analytics query against NSGFlowLog_CL, and gate new peering creation behind an Azure Policy that requires an explicit deny rule below priority 4096 on any NSG attached to a production subnet.
- Blast Radius
- Any subnet peered directly to a compromised VNet is reachable on all protocols and ports, not just the ports the application requires
- Private endpoints and PaaS resources (SQL, Storage, Key Vault) exposed via service endpoints inherit the same VirtualNetwork-tag reachability
- East-west traffic bypasses any centralised NVA or Azure Firewall inspection because peering routes traffic directly between spokes
- Newly peered VNets automatically inherit exposure without any NSG change, since the tag recalculates on every peering addition
- Incident scope expands from a single compromised host to every production tier within peering distance, defeating VNet-based segmentation assumptions
Apply the intervention
Before you execute
Confirm the incident scope, obtain approved privileged access, and capture the current configuration before applying changes.
Fix commands and configuration
# Enumerate effective rules on the affected NIC az network nic list-effective-nsg --resource-group rg-spoke-prod --name nic-app01 --query "value[].effectiveSecurityRules[?name=='AllowVnetInBound']" # Insert an explicit deny beneath the default rule priority band az network nsg rule create --resource-group rg-spoke-prod --nsg-name nsg-prod-web --name Deny-Lateral-VNet --priority 100 --direction Inbound --access Deny --protocol '*' --source-address-prefixes VirtualNetwork --destination-address-prefixes '*' --destination-port-ranges '*' # Re-permit only the specific required flows using Application Security Groups az network asg create --resource-group rg-spoke-prod --name asg-app-tier az network nsg rule create --resource-group rg-spoke-prod --nsg-name nsg-prod-web --name Allow-App-Tier-443 --priority 90 --direction Inbound --access Allow --protocol Tcp --source-asgs asg-app-tier --destination-port-ranges 443 # Stop forwarding traffic across peerings that don't require it az network vnet peering update --resource-group rg-hub --vnet-name vnet-hub --name peer-to-spoke-dev --set allowForwardedTraffic=falseVerify, roll back or escalate
Verify recovery
Repeat the detection checks after the intervention and confirm that the original symptom no longer occurs.
Rollback
Stop if impact increases. Restore the captured pre-change configuration or approved backup, then reassess before retrying.
Escalate when
Escalate to the platform owner or security incident lead when recovery is unavailable, scope is uncertain, or privileged access cannot be verified.