Back to Checks
azure

Network security group does not allow inbound SSH (TCP port 22) from the Internet

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

Azure NSG inbound rules that allow SSH on TCP 22 from 0.0.0.0/0, Internet, or * are identified, including rules where port ranges include 22 and protocol is TCP or *.

Indicates NSGs exposing SSH to the Internet.

Risk

Public SSH access weakens confidentiality and integrity. Open 22 invites brute force and key theft, enabling remote shell control, persistence, and lateral movement. Attackers can pivot into VNets, exfiltrate data, deploy crypto-miners, and impact availability.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks network_ssh_internet_access_restricted

Recommendation

Apply least privilege on SSH:

  • Remove public rules to TCP 22 from 0.0.0.0/0
  • Allowlist specific admin IPs or management subnets
  • Prefer Azure Bastion, JIT access, or VPN/ExpressRoute for admin
  • Use key-based auth, disable passwords, and remove unnecessary public IPs

Adopt defense in depth with logging and periodic reviews.

Remediation

CLI

az network nsg rule delete --resource-group <RESOURCE_GROUP> --nsg-name <NSG_NAME> --name <RULE_NAME>

Terraform

# Restrict SSH to a specific source so port 22 is not open to the Internet
resource "azurerm_network_security_rule" "<example_resource_name>" {
  name                        = "<example_rule_name>"
  resource_group_name         = "<example_resource_name>"
  network_security_group_name = "<example_resource_name>"
  priority                    = 100
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "22"
  source_address_prefix       = "<ALLOWED_CIDR>" # CRITICAL: restrict SSH source; not Internet/*/0.0.0.0/0
  destination_address_prefix  = "*"
}

NativeIaC

// NSG allowing SSH only from a specific source (not the Internet)
resource <example_resource_name> 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
  name: '<example_resource_name>'
  location: '<location>'
  properties: {
    securityRules: [
      {
        name: '<example_rule_name>'
        properties: {
          priority: 100
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '22'
          sourceAddressPrefix: '<ALLOWED_CIDR>' // CRITICAL: restrict SSH source; not Internet/*/0.0.0.0/0
          destinationAddressPrefix: '*'
        }
      }
    ]
  }
}

Other

1In Azure Portal, go to Network security groups and open <NSG_NAME>
2Select Inbound security rules
3Find any rule that allows TCP 22 from Internet/Any/0.0.0.0/0
4Delete the rule, or Edit it and set Source to IP Addresses with only your allowed CIDR(s)
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_ssh_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 SSH internet access restricted."
                rule_fail_condition = any(
                    (
                        rule.destination_port_range == "22"
                        or (
                            (
                                rule.destination_port_range
                                and "-" in rule.destination_port_range
                            )
                            and int(rule.destination_port_range.split("-")[0]) <= 22
                            and int(rule.destination_port_range.split("-")[1]) >= 22
                        )
                    )
                    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 SSH internet access allowed."
                findings.append(report)

        return findings