Back to blog
AzureBest PracticesCloud SecurityNetworking

NSG Allows All Inbound Traffic: Why It's Dangerous and How to Fix It

An Azure NSG rule that allows all inbound traffic from the internet exposes every port to attackers. Learn how to detect, fix, and prevent it.

TL;DR

This check flags Network Security Group rules that allow inbound traffic from any source (0.0.0.0/0 or Internet) across all ports. Lock the rule down to specific ports and trusted source ranges, or remove it and rely on tighter scoped rules instead.

An NSG rule that allows all inbound traffic is one of the fastest ways to turn a private workload into an internet-facing target. It happens more often than you would expect: someone opens things up to debug a connectivity issue, the deadline hits, and the temporary "allow all" rule never gets reverted. Lensix catches this with the nsg_openallports check, and this post walks through what it means, why it is dangerous, and how to close the gap for good.


What this check detects

Azure Network Security Groups (NSGs) filter network traffic to and from Azure resources inside a virtual network. Each NSG contains a list of security rules, and every rule specifies a direction, a protocol, a port range, a source, and a destination.

The nsg_openallports check fires when an NSG contains an inbound allow rule with a source that maps to the public internet and a port range that covers everything. In practice it looks for rules where:

  • Direction is Inbound
  • Access is Allow
  • Source address prefix is 0.0.0.0/0, *, or Internet
  • Destination port range is * or a wide span like 0-65535

Note: Azure evaluates NSG rules by priority, lowest number first. A permissive allow rule at priority 100 will be matched before a more restrictive deny rule at priority 4096, so the order matters as much as the rule itself.

A rule like the one below is exactly what the check is built to find:

{
  "name": "allow-all-inbound",
  "properties": {
    "priority": 100,
    "direction": "Inbound",
    "access": "Allow",
    "protocol": "*",
    "sourceAddressPrefix": "*",
    "sourcePortRange": "*",
    "destinationAddressPrefix": "*",
    "destinationPortRange": "*"
  }
}

Why it matters

Opening every inbound port to the entire internet removes the network as a security layer. Every service listening on the VM or subnet is now reachable by anyone who can find the public IP, and finding it is trivial. Mass scanners like Shodan and Censys index exposed Azure IP ranges continuously, and automated bots probe new public endpoints within minutes of them coming online.

Here is what that exposure turns into:

  • RDP and SSH brute forcing. Ports 3389 and 22 are the most attacked ports on the internet. An exposed Windows VM with RDP open will see credential-stuffing attempts almost immediately, and weak or reused passwords fall fast.
  • Database theft. Open ports for SQL Server (1433), MySQL (3306), MongoDB (27017), or Redis (6379) expose data stores that were never meant to face the internet. Unauthenticated Redis and MongoDB instances have been wiped and ransomed at scale.
  • Lateral movement. Once an attacker lands on one exposed host, an NSG that allows everything gives them a clear path to pivot deeper into the subnet.
  • Cryptomining and botnet recruitment. Compromised VMs get drafted into mining operations or DDoS botnets, which shows up later as a surprise on your Azure bill.

Danger: An "allow all inbound from any" rule combined with a public IP and a service running as root or LocalSystem is a full-host-compromise waiting to happen. Treat any finding from this check as time-sensitive, not a backlog item.

The business impact compounds. Beyond the breach itself, exposing data stores to the public internet violates controls in PCI DSS, SOC 2, ISO 27001, and the Azure CIS Benchmark, which can derail an audit or contract.


How to fix it

The goal is to replace the wide-open rule with rules that allow only the ports you need from only the sources that need them. Start by identifying the offending rule.

1. Find the rule

# List NSGs in a resource group
az network nsg list \
  --resource-group my-rg \
  --query "[].name" -o tsv

# Inspect the rules on a specific NSG
az network nsg rule list \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --query "[?direction=='Inbound' && access=='Allow'].{Name:name, Priority:priority, Source:sourceAddressPrefix, Ports:destinationPortRange}" \
  -o table

2. Understand what the rule is actually serving

Before you touch anything, confirm which ports and clients legitimately need access. Check what is listening on the host and who connects to it. Removing access blindly can take down a production service, so this step matters.

Warning: Tightening an NSG rule changes live traffic flow. If a service genuinely depends on the open rule, scoping it down will cut connectivity for clients outside the new allowed range. Validate dependencies and roll out during a maintenance window where possible.

3. Replace the open rule with scoped rules

Delete the all-traffic rule and add targeted rules. The example below allows HTTPS from anywhere (a reasonable case for a public web server) and SSH only from a corporate range.

Danger: The command below deletes a live NSG rule and will immediately change what traffic is permitted. Confirm you have replacement rules ready before running it against a production NSG.

# Remove the permissive rule
az network nsg rule delete \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-all-inbound

# Allow HTTPS from the internet (web tier only)
az network nsg rule create \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-https \
  --priority 200 \
  --direction Inbound \
  --access Allow \
  --protocol Tcp \
  --source-address-prefixes "*" \
  --destination-port-ranges 443

# Allow SSH only from a trusted office range
az network nsg rule create \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-ssh-office \
  --priority 210 \
  --direction Inbound \
  --access Allow \
  --protocol Tcp \
  --source-address-prefixes 203.0.113.0/24 \
  --destination-port-ranges 22

Tip: For administrative access to VMs, skip public SSH and RDP entirely. Use Azure Bastion for browser-based RDP/SSH over TLS, or Just-in-Time VM access in Microsoft Defender for Cloud, which opens the management port only for a requested window and locks it back down automatically.

4. Fix it in the Azure Portal (if you prefer the console)

  1. Open Network security groups and select the affected NSG.
  2. Under Settings, choose Inbound security rules.
  3. Find the rule with source Any and destination port range *.
  4. Click the rule, change the Source to a specific IP range or service tag, and set Destination port ranges to the exact ports needed.
  5. Save, then verify the rule list no longer shows an Any-to-all-ports allow.

How to prevent it from coming back

Fixing one NSG is housekeeping. Stopping the pattern from recurring is the real win, and that means catching the bad rule before it ever reaches production.

Define NSGs as code

Manage NSG rules in Terraform or Bicep so every change goes through review. Here is a Terraform rule that is scoped from the start:

resource "azurerm_network_security_rule" "allow_https" {
  name                        = "allow-https"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "443"
  source_address_prefix       = "*"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.main.name
  network_security_group_name = azurerm_network_security_group.web.name
}

Gate it in CI/CD

Run a policy-as-code scanner against your IaC on every pull request. Checkov, for example, ships with a built-in rule (CKV_AZURE_10 and related) that fails the build when an NSG exposes ports like 22 or 3389 to the internet.

# Scan Terraform before merge
checkov -d ./infra --framework terraform

# Fail the pipeline on any NSG public-exposure finding
checkov -d ./infra --check CKV_AZURE_9,CKV_AZURE_10,CKV_AZURE_77

Enforce with Azure Policy

For runtime guardrails, assign a built-in Azure Policy definition that audits or denies overly permissive NSG rules. Azure ships definitions such as "Management ports should be closed on your virtual machines" and "All network ports should be restricted on network security groups associated to your virtual machine." Set the effect to Deny on production subscriptions to block the change at the API level.

Tip: Combine a Deny policy for management ports with an Audit policy for broader exposure. Deny stops the worst cases outright, while audit gives you a continuous inventory of anything that drifted, which Lensix can track over time.


Best practices

  • Default deny. Build NSGs so that nothing inbound is allowed unless a rule explicitly permits it. NSGs already deny by default at low priority, so do not undo that with a broad allow rule.
  • One port, one purpose. Avoid wildcard port ranges. List the exact ports a workload needs, even if it means a few more rules.
  • Use service tags instead of raw CIDRs. Tags like AzureLoadBalancer, VirtualNetwork, and Storage express intent clearly and stay current as Azure's IP ranges change.
  • Keep management ports off the public internet. Front RDP and SSH with Azure Bastion or Just-in-Time access, never a static public allow rule.
  • Layer your controls. NSGs are one layer. Combine them with Azure Firewall, application gateways with WAF, and host-level firewalls so a single misconfiguration is not a single point of failure.
  • Enable NSG flow logs. Send flow logs to a Log Analytics workspace so you can see what is actually hitting your rules and prune anything unused.
  • Review rules on a schedule. Temporary rules have a way of becoming permanent. Audit NSGs regularly and remove anything that no longer has an owner or a reason to exist.

An open NSG rule is a small line in a config file with an outsized blast radius. Scope it down, codify the safe version, and put a gate in front of your pipeline so the next "temporary" allow-all never makes it past review.