Back to Checks
azure

Network security group does not allow inbound RDP (TCP 3389) from the Internet

network_rdp_internet_access_restricted
Service: networkSeverity: high
OfficialBuilt-inVerified
Categories:internet-exposed
Resource Type:microsoft.network/networksecuritygroups

Azure NSG inbound rules are evaluated for public RDP exposure. The finding flags rules that Allow TCP traffic to port 3389 from broad sources like 0.0.0.0/0, Internet, or *, including ranges that cover 3389.

Risk

Exposed RDP enables Internet-wide brute force and credential stuffing, risking unauthorized console access.

Compromise can cause data theft (confidentiality), tampering or malware deployment (integrity), VM lockout or disruption (availability), and lateral movement within the VNet.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks network_rdp_internet_access_restricted

Recommendation

Enforce least privilege for remote admin:

  • Remove Allow to 3389 from 0.0.0.0/0
  • Limit access to fixed IPs or private networks
  • Prefer Azure Bastion, JIT, or VPN/ExpressRoute
  • Harden auth (strong keys, MFA) and monitor

Adopt zero trust and defense in depth.

Remediation

CLI

az network nsg rule delete --resource-group <RESOURCE_GROUP> --nsg-name <example_resource_name> --name <RDP_RULE_NAME>

Terraform

# NSG with RDP allowed only from a restricted CIDR (not Internet)
resource "azurerm_network_security_group" "<example_resource_name>" {
  name                = "<example_resource_name>"
  location            = "<region>"
  resource_group_name = "<example_resource_name>"

  security_rule {
    name                       = "Allow-RDP-Restricted"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    destination_address_prefix = "*"
    source_address_prefix      = "<AUTHORIZED_CIDR>" # CRITICAL: restrict source; not "*", "Internet", or "0.0.0.0/0" so the check passes
  }
}

NativeIaC

// NSG with RDP allowed only from a restricted CIDR (not Internet)
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
  name: '<example_resource_name>'
  location: '<region>'
  properties: {
    securityRules: [
      {
        name: 'Allow-RDP-Restricted'
        properties: {
          priority: 100
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '3389'
          destinationAddressPrefix: '*'
          sourceAddressPrefix: '<AUTHORIZED_CIDR>' // CRITICAL: restrict source; not "Internet", "*", or "0.0.0.0/0" to pass the check
        }
      }
    ]
  }
}

Other

1In Azure Portal, go to Network Security Groups and open the NSG attached to the resource
2Select Inbound security rules
3Find any rule that allows TCP 3389 with Source set to Any/Internet/*/0.0.0.0/0
4Delete the rule, or edit it and set Source to a specific IP/CIDR (e.g., <AUTHORIZED_CIDR>)
5Save

Check Code

from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.network.network_client import network_client


class network_rdp_internet_access_restricted(Check):
    def execute(self) -> Check_Report_Azure:
        findings = []
        for subscription, security_groups in network_client.security_groups.items():
            subscription_name = network_client.subscriptions.get(
                subscription, subscription
            )
            for security_group in security_groups:
                report = Check_Report_Azure(
                    metadata=self.metadata(), resource=security_group
                )
                report.subscription = subscription
                report.status = "PASS"
                report.status_extended = f"Security Group {security_group.name} from subscription {subscription_name} ({subscription}) has RDP internet access restricted."
                rule_fail_condition = any(
                    (
                        rule.destination_port_range == "3389"
                        or (
                            (
                                rule.destination_port_range
                                and "-" in rule.destination_port_range
                            )
                            and int(rule.destination_port_range.split("-")[0]) <= 3389
                            and int(rule.destination_port_range.split("-")[1]) >= 3389
                        )
                    )
                    and rule.protocol in ["TCP", "Tcp", "*"]
                    and rule.source_address_prefix in ["Internet", "*", "0.0.0.0/0"]
                    and rule.access == "Allow"
                    and rule.direction == "Inbound"
                    for rule in security_group.security_rules
                )
                if rule_fail_condition:
                    report.status = "FAIL"
                    report.status_extended = f"Security Group {security_group.name} from subscription {subscription_name} ({subscription}) has RDP internet access allowed."
                findings.append(report)

        return findings