Back to blog
AzureBest PracticesCloud SecurityIdentity & AccessNetworking

NSG Allows Public RDP: Why Open Port 3389 Is a Ransomware Magnet

Learn why an Azure NSG rule allowing public RDP on port 3389 is a top ransomware vector, plus step-by-step CLI, Bastion, and policy fixes to lock it down.

TL;DR

This check flags Azure Network Security Group rules that allow inbound RDP (port 3389) from public internet ranges like 0.0.0.0/0 or Internet. Open RDP is one of the most exploited entry points for ransomware and brute-force attacks. Lock it down to a bastion host, VPN, or a specific corporate IP range, or replace it with Azure Bastion entirely.

RDP exposed to the open internet is the cloud equivalent of leaving your front door unlocked with a sign that says "valuables inside." Attackers run automated scanners across the entire IPv4 space looking for port 3389, and a single misconfigured Network Security Group rule is all they need to start hammering your VM with credential stuffing and brute-force attempts.

The nsg_openrdp check inspects your Azure NSG rules and raises a finding whenever a rule permits inbound RDP traffic from a public source. This post explains exactly what it looks for, why it matters more than most people assume, and how to fix and prevent it for good.


What this check detects

Lensix evaluates every inbound security rule in your Network Security Groups and flags any rule that meets all of the following conditions:

  • Direction is Inbound
  • Access is Allow
  • Protocol is TCP or *
  • Destination port range includes 3389 (directly, as a range, or via *)
  • Source is a public range such as 0.0.0.0/0, Internet, *, or any prefix that isn't restricted to private or known address space

A rule like the one below is a textbook match:

{
  "name": "allow-rdp",
  "properties": {
    "priority": 1000,
    "direction": "Inbound",
    "access": "Allow",
    "protocol": "Tcp",
    "sourceAddressPrefix": "*",
    "sourcePortRange": "*",
    "destinationAddressPrefix": "*",
    "destinationPortRange": "3389"
  }
}

Note: A Network Security Group is Azure's stateful packet filter. It can be attached to a subnet or directly to a NIC. When attached to a subnet, the rule applies to every VM in that subnet, which means a single overly permissive RDP rule can expose dozens of machines at once.


Why it matters

RDP brute-forcing is not a theoretical risk. It is one of the most common initial-access vectors in real ransomware incidents. Microsoft and incident response firms have repeatedly reported that exposed RDP accounts for a large share of human-operated ransomware intrusions.

Here is how a typical attack unfolds:

  1. An automated scanner finds your VM's public IP responding on port 3389.
  2. The attacker launches a dictionary or credential-stuffing attack against common usernames like administrator, admin, or service account names.
  3. Once they guess a valid credential, or reuse one leaked from another breach, they have an interactive desktop session on your machine.
  4. From there they disable defenses, move laterally, exfiltrate data, and deploy ransomware.

Beyond ransomware, open RDP exposes you to known protocol vulnerabilities. BlueKeep (CVE-2019-0708) was a wormable RDP flaw severe enough that Microsoft issued patches for end-of-life operating systems. If your VM is internet-reachable on 3389 and falls behind on patching, you are one exploit away from compromise with no credentials required.

Danger: An open RDP port combined with weak or reused passwords is a complete breach waiting to happen. Treat any finding from this check as high priority, not a backlog item.

The business impact is straightforward: downtime, ransom demands, data loss, regulatory penalties under frameworks like GDPR or HIPAA, and the reputational damage that follows a public breach disclosure.


How to fix it

You have three good options, listed from quickest to most robust. Pick based on how the VM is actually accessed.

Option 1: Remove the rule entirely (if RDP is not needed)

If nobody actually needs direct RDP, delete the rule. This is the safest outcome.

Danger: Deleting an NSG rule takes effect immediately. Confirm you are not removing your own active management path before running this against a production NSG.

az network nsg rule delete \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-rdp

Option 2: Restrict the source to a known IP range

If RDP is genuinely required from a corporate office or VPN egress, narrow the source prefix to that specific range instead of the whole internet.

az network nsg rule update \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-rdp \
  --source-address-prefixes 203.0.113.25/32 \
  --destination-port-ranges 3389 \
  --protocol Tcp

Warning: Pinning to a corporate IP is better than open access, but residential and many office ISP addresses are dynamic. If the range changes you will lock yourself out, and a wide corporate CIDR still exposes you to any attacker who can route from inside that block.

Option 3: Replace RDP exposure with Azure Bastion (recommended)

The strongest fix is to stop exposing 3389 on the public internet at all. Azure Bastion provides RDP and SSH connectivity to your VMs directly through the Azure portal over TLS, so the VM never needs a public IP or an open RDP port.

Create a dedicated subnet and the Bastion host:

# Bastion requires a subnet named exactly AzureBastionSubnet, /26 or larger
az network vnet subnet create \
  --resource-group my-rg \
  --vnet-name my-vnet \
  --name AzureBastionSubnet \
  --address-prefixes 10.0.1.0/26

az network public-ip create \
  --resource-group my-rg \
  --name bastion-pip \
  --sku Standard \
  --location eastus

az network bastion create \
  --resource-group my-rg \
  --name my-bastion \
  --public-ip-address bastion-pip \
  --vnet-name my-vnet \
  --location eastus

Then remove the public RDP rule using Option 1 and connect through the portal's Connect > Bastion button.

Warning: Azure Bastion runs continuously and bills per hour plus outbound data, so the Standard SKU is not free. For environments with many VMs the per-host cost is usually well worth eliminating internet-facing management ports, but budget for it.

Verify the fix from the console

  1. Open the Azure portal and go to Network security groups.
  2. Select the NSG and open Inbound security rules.
  3. Confirm no rule allows source Any, Internet, or 0.0.0.0/0 to destination port 3389.

How to prevent it from happening again

Manual fixes do not stick. The same open rule reappears the next time someone spins up a VM in a hurry. Push the guardrails left into infrastructure as code and policy.

Catch it in Terraform before deploy

Define RDP rules with explicit, restricted sources in your modules and never default to wildcards:

resource "azurerm_network_security_rule" "rdp" {
  name                        = "allow-rdp-from-vpn"
  priority                    = 1000
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "3389"
  source_address_prefix       = "203.0.113.25/32"  # never "*" or "Internet"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.main.name
  network_security_group_name = azurerm_network_security_group.main.name
}

Enforce with Azure Policy

Azure has a built-in policy definition, "RDP access from the Internet should be blocked," that flags or denies NSGs allowing 3389 from any source. Assign it at the management group or subscription scope so every new resource is evaluated automatically.

az policy assignment create \
  --name 'deny-public-rdp' \
  --display-name 'Block public RDP on NSGs' \
  --policy 'e372f825-a257-4fb8-9175-797a8a8627d6' \
  --scope '/subscriptions/<subscription-id>'

Tip: Run a static analysis tool like tfsec, checkov, or terrascan as a required CI/CD gate. A single rule blocking destination_port_range = "3389" with a wildcard source will stop the misconfiguration before it ever reaches Azure. Pair it with Lensix continuous scanning to catch drift introduced through the portal.

Gate it in your pipeline

Add a Checkov scan to your build so a pull request fails when someone introduces an open RDP rule:

checkov -d ./infra --framework terraform \
  --check CKV_AZURE_9,CKV_AZURE_10

Best practices

  • Never expose management ports to the internet. RDP (3389) and SSH (22) should always be reached through Bastion, a VPN, or Azure Private Link, not a public NSG allow rule.
  • Default deny, allow narrowly. Start every NSG from a deny-all posture and open only the specific ports and sources you can justify.
  • Use just-in-time VM access. Microsoft Defender for Cloud can open RDP only on demand for a limited time window and a specific source IP, then close it automatically.
  • Avoid the * wildcard for source, port, and protocol. Be explicit so a reviewer can see exactly what each rule permits.
  • Enforce strong authentication. Even when access is restricted, require complex passwords, account lockout policies, and MFA where possible.
  • Monitor and alert. Send NSG flow logs and sign-in logs to a SIEM, and alert on repeated failed RDP attempts.
  • Audit continuously. Misconfigurations creep back in through manual changes. Run automated checks on a schedule, not just at deploy time.

The cheapest security control is the door you never opened. If a VM does not need to be reachable on 3389 from the internet, the right number of public RDP rules is zero.

Fixing this check is usually a five-minute job. The breach it prevents is not. Close the port, route management traffic through Bastion or a VPN, and lock the configuration in with policy so it stays closed.