Network ACL Allows SSH (Port 22) from the Internet
Overview
This check identifies Network Access Control Lists (NACLs) that have inbound rules allowing SSH traffic (TCP port 22) from 0.0.0.0/0 (the entire internet). NACLs act as stateless firewalls at the subnet boundary, controlling traffic entering or leaving your subnets.
Allowing SSH from anywhere at the network level exposes your instances to direct attack attempts before security groups even come into play. This check ensures SSH access is restricted to trusted sources.
Risk
Allowing unrestricted SSH access from the internet creates significant security risks:
- Brute-force attacks: Automated bots constantly scan for open SSH ports and attempt to guess credentials
- Credential stuffing: Attackers use stolen username/password combinations to try gaining access
- Vulnerability exploitation: Any SSH implementation bugs become immediately exploitable
- Reconnaissance: Attackers can probe your systems to gather information for future attacks
- Compliance violations: Many security frameworks (CIS, PCI-DSS, SOC 2) require restricting SSH access
This is a medium severity finding because it weakens your defense-in-depth posture.
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to modify VPC Network ACLs
- Knowledge of which IP ranges legitimately need SSH access to your subnets
Required IAM permissions
Your IAM user or role needs these permissions:
ec2:DescribeNetworkAclsec2:ReplaceNetworkAclEntryec2:DeleteNetworkAclEntryec2:CreateNetworkAclEntry
AWS Console Method
-
Open the VPC Console
- Go to VPC Console in us-east-1
-
Navigate to Network ACLs
- In the left sidebar, click Network ACLs
-
Find the affected NACL
- Locate the Network ACL flagged in the Prowler finding
- Click on it to select it
-
Review the Inbound Rules
- Click the Inbound rules tab
- Look for rules with these characteristics:
- Source:
0.0.0.0/0or::/0 - Port range:
22 - Protocol:
TCP (6) - Action:
Allow
- Source:
-
Choose your remediation approach:
Option A: Restrict SSH to trusted IP ranges (Recommended)
- Click Edit inbound rules
- Find the rule allowing SSH from
0.0.0.0/0 - Change the Source to your trusted CIDR block (e.g.,
10.0.0.0/8for internal networks or your office IP range) - Click Save changes
Option B: Delete the SSH rule entirely
- Click Edit inbound rules
- Click Remove next to the SSH rule allowing
0.0.0.0/0 - Click Save changes
- Consider using AWS Systems Manager Session Manager instead of SSH
Option C: Change the rule to deny
- Click Edit inbound rules
- Change the Allow/Deny to
Denyfor the0.0.0.0/0SSH rule - Click Save changes
Warning: Before modifying rules, ensure you have an alternative way to access your instances (like a VPN, bastion host, or Session Manager). Blocking SSH could lock you out.
AWS CLI (optional)
Step 1: Identify the problematic rules
List all Network ACLs and their rules:
aws ec2 describe-network-acls \
--region us-east-1 \
--query 'NetworkAcls[*].{ID:NetworkAclId,VpcId:VpcId,Entries:Entries}' \
--output json
Look for inbound entries where:
CidrBlockis0.0.0.0/0Protocolis6(TCP)PortRange.Fromis22andPortRange.Tois22RuleActionisallowEgressisfalse(indicating inbound)
Step 2: Option A - Replace with a restricted CIDR block
Replace the rule to allow SSH only from trusted networks:
aws ec2 replace-network-acl-entry \
--network-acl-id <nacl-id> \
--ingress \
--rule-number <rule-number> \
--protocol 6 \
--port-range From=22,To=22 \
--cidr-block 10.0.0.0/8 \
--rule-action allow \
--region us-east-1
Step 2: Option B - Delete the permissive rule
Remove the rule entirely:
aws ec2 delete-network-acl-entry \
--network-acl-id <nacl-id> \
--ingress \
--rule-number <rule-number> \
--region us-east-1
Step 2: Option C - Replace with a deny rule
Change the rule to explicitly deny SSH from the internet:
aws ec2 replace-network-acl-entry \
--network-acl-id <nacl-id> \
--ingress \
--rule-number <rule-number> \
--protocol 6 \
--port-range From=22,To=22 \
--cidr-block 0.0.0.0/0 \
--rule-action deny \
--region us-east-1
CloudFormation (optional)
This template creates a Network ACL with SSH restricted to trusted networks only:
AWSTemplateFormatVersion: '2010-09-09'
Description: Secure Network ACL with SSH restricted to trusted networks
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID where the Network ACL will be created
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: Subnet ID to associate with this Network ACL
TrustedSshCidrBlock:
Type: String
Description: CIDR block allowed for SSH access (e.g., VPN or bastion subnet)
Default: 10.0.0.0/8
Resources:
SecureNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: secure-ssh-nacl
# Allow SSH from trusted networks only
InboundSshRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref SecureNetworkAcl
RuleNumber: 100
Protocol: 6
RuleAction: allow
CidrBlock: !Ref TrustedSshCidrBlock
PortRange:
From: 22
To: 22
# Deny SSH from the internet (explicit deny)
InboundSshDenyRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref SecureNetworkAcl
RuleNumber: 101
Protocol: 6
RuleAction: deny
CidrBlock: 0.0.0.0/0
PortRange:
From: 22
To: 22
# Allow HTTPS inbound from anywhere (for web servers)
InboundHttpsRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref SecureNetworkAcl
RuleNumber: 110
Protocol: 6
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 443
To: 443
# Allow ephemeral ports for return traffic
InboundEphemeralRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref SecureNetworkAcl
RuleNumber: 140
Protocol: 6
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 1024
To: 65535
# Allow all outbound traffic
OutboundAllRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref SecureNetworkAcl
RuleNumber: 100
Protocol: -1
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
# Associate NACL with subnet
SubnetNetworkAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref SubnetId
NetworkAclId: !Ref SecureNetworkAcl
Outputs:
NetworkAclId:
Description: ID of the secure Network ACL
Value: !Ref SecureNetworkAcl
Deploy with:
aws cloudformation deploy \
--template-file secure-ssh-nacl.yaml \
--stack-name secure-ssh-network-acl \
--parameter-overrides \
VpcId=vpc-12345678 \
SubnetId=subnet-12345678 \
TrustedSshCidrBlock=10.0.0.0/8 \
--region us-east-1
Terraform (optional)
variable "vpc_id" {
description = "VPC ID where the Network ACL will be created"
type = string
}
variable "subnet_ids" {
description = "List of subnet IDs to associate with this Network ACL"
type = list(string)
}
variable "trusted_ssh_cidr_block" {
description = "CIDR block allowed for SSH access"
type = string
default = "10.0.0.0/8"
}
resource "aws_network_acl" "secure_ssh" {
vpc_id = var.vpc_id
subnet_ids = var.subnet_ids
tags = {
Name = "secure-ssh-nacl"
}
}
# Allow SSH from trusted networks only
resource "aws_network_acl_rule" "inbound_ssh_allow" {
network_acl_id = aws_network_acl.secure_ssh.id
rule_number = 100
egress = false
protocol = "tcp"
rule_action = "allow"
cidr_block = var.trusted_ssh_cidr_block
from_port = 22
to_port = 22
}
# Explicitly deny SSH from the internet
resource "aws_network_acl_rule" "inbound_ssh_deny" {
network_acl_id = aws_network_acl.secure_ssh.id
rule_number = 101
egress = false
protocol = "tcp"
rule_action = "deny"
cidr_block = "0.0.0.0/0"
from_port = 22
to_port = 22
}
# Allow HTTPS inbound from anywhere
resource "aws_network_acl_rule" "inbound_https" {
network_acl_id = aws_network_acl.secure_ssh.id
rule_number = 110
egress = false
protocol = "tcp"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
# Allow ephemeral ports for return traffic
resource "aws_network_acl_rule" "inbound_ephemeral" {
network_acl_id = aws_network_acl.secure_ssh.id
rule_number = 140
egress = false
protocol = "tcp"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
# Allow all outbound traffic
resource "aws_network_acl_rule" "outbound_all" {
network_acl_id = aws_network_acl.secure_ssh.id
rule_number = 100
egress = true
protocol = "-1"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
}
output "network_acl_id" {
description = "ID of the secure Network ACL"
value = aws_network_acl.secure_ssh.id
}
Deploy with:
terraform init
terraform plan -var="vpc_id=vpc-12345678" -var='subnet_ids=["subnet-12345678"]'
terraform apply -var="vpc_id=vpc-12345678" -var='subnet_ids=["subnet-12345678"]'
Verification
After remediation, verify the rules have been corrected:
-
In the AWS Console:
- Go to VPC > Network ACLs
- Select the remediated NACL
- Click the Inbound rules tab
- Confirm there are no rules allowing
0.0.0.0/0to TCP port 22
-
Test your access:
- Verify that SSH still works from your trusted networks (VPN, bastion, etc.)
- Confirm that SSH is blocked from the public internet
CLI verification commands
Check the NACL rules:
aws ec2 describe-network-acls \
--network-acl-ids <nacl-id> \
--region us-east-1 \
--query 'NetworkAcls[0].Entries[?Egress==`false` && Protocol==`6` && PortRange.From==`22`]'
The output should not show any entries with:
CidrBlock: 0.0.0.0/0RuleAction: allow
Re-run the Prowler check to confirm:
prowler aws -c ec2_networkacl_allow_ingress_tcp_port_22 --region us-east-1
Additional Resources
- AWS Documentation: Network ACLs
- AWS Documentation: Control Traffic to Subnets Using Network ACLs
- AWS Systems Manager Session Manager - A more secure alternative to SSH
- CIS AWS Foundations Benchmark - Industry security standards
Notes
- Defense in depth: NACLs are just one layer. Security groups should also restrict SSH access for granular instance-level protection.
- Better alternatives to open SSH:
- AWS Systems Manager Session Manager: Shell access without opening any inbound ports
- Bastion hosts: A hardened jump server in a separate subnet with restricted access
- VPN: Require VPN connection before SSH is reachable
- Stateless nature: NACLs are stateless. The ephemeral port rule (1024-65535) handles return traffic for outbound SSH connections.
- Rule evaluation order: NACL rules are evaluated by rule number (lowest first). Place deny rules for
0.0.0.0/0after allow rules for trusted CIDRs. - IPv6 consideration: If your VPC uses IPv6, also check for rules allowing
::/0to port 22 and apply the same restrictions. - Audit regularly: SSH access requirements change over time. Review NACL rules periodically to ensure they remain appropriate.