Description
Ensure that no VPC network ACL allows inbound traffic from the Internet (0.0.0.0/0 or ::/0) to SSH (TCP port 22). Network ACLs control traffic at the subnet boundary, and their inbound rules are evaluated in order of rule number, starting with the lowest.
Rationale
Network ACLs are a subnet-level layer of defense in depth in front of security groups. If a network ACL allows SSH from the whole Internet, a single misconfigured security group or route can expose instances to brute-force, credential stuffing and SSH exploit attempts. Denying SSH from 0.0.0.0/0 and ::/0 at the subnet boundary, and using AWS Systems Manager Session Manager or a bastion host behind a VPN for administration, keeps this layer effective.
Impact
Blocks SSH from the Internet at the subnet level, in addition to security groups
Reduces the impact of a misconfigured security group
Administrators currently connecting to SSH over the Internet will lose access and must use Session Manager, a VPN or a bastion host in an allowed range
Because network ACLs are stateless, a rule that is too broad can block legitimate traffic, so changes should be tested on a non-critical subnet first
Default Value
The default network ACL of a VPC allows all inbound traffic from 0.0.0.0/0 (rule 100), and also from ::/0 if the VPC has an IPv6 CIDR block, so it allows SSH from the Internet unless a deny rule is added. A newly created custom network ACL denies all inbound traffic until rules are added.
Pre-Requisites
IAM permissions: ec2:DescribeNetworkAcls, ec2:CreateNetworkAclEntry, ec2:ReplaceNetworkAclEntry and ec2:DeleteNetworkAclEntry
The subnets associated with the network ACL and any trusted ranges that legitimately need SSH access
A free rule number lower than the rule that currently allows the traffic
Remediation
Test Plan
Console:
Sign in to the AWS Management Console at https://console.aws.amazon.com.
Open the Amazon VPC console and select Network ACLs.
Select a network ACL and open the Inbound rules tab.
Read the rules in order of rule number. For each source 0.0.0.0/0 and ::/0, find the first rule that matches port 22, meaning:
type All traffic, or
protocol TCP with a port range that includes 22.
Repeat for each network ACL in each region in use.
If that first matching rule is Allow for either source, follow the implementation steps.
CLI:
List the network ACLs:
aws ec2 describe-network-acls --region <REGION> --query "NetworkAcls[].[NetworkAclId,VpcId,IsDefault]" --output table
List the inbound rules of a network ACL:
aws ec2 describe-network-acls --region <REGION> --network-acl-ids <NETWORK_ACL_ID> --query "NetworkAcls[].Entries[?Egress==\`false\`].[RuleNumber,Protocol,RuleAction,CidrBlock,Ipv6CidrBlock,PortRange.From,PortRange.To]" --output table
The network ACL fails if, for source 0.0.0.0/0 or ::/0, the lowest-numbered rule with protocol -1 (all traffic), or protocol 6 (TCP) with a port range that includes 22, has the action allow.
Implementation Steps
Console:
Sign in to the AWS Management Console at https://console.aws.amazon.com.
Open the Amazon VPC console and select Network ACLs.
Select the network ACL found in the Test Plan and open the Inbound rules tab.
Choose Edit inbound rules.
If the matching allow rule is a rule only for port 22, delete it.
If the matching allow rule is broader (for example All traffic), choose Add new rule and create a deny rule with a lower rule number:
Type: Custom TCP, Port range: 22,
Source: 0.0.0.0/0,
Allow/Deny: Deny.
If the network ACL has an IPv6 allow rule, add the same deny rule with source ::/0.
If SSH is needed from trusted ranges, add allow rules for those ranges with rule numbers lower than the deny rules.
Choose Save changes.
Repeat for each affected network ACL in each region in use.
CLI:
The provided command replaces an existing rule with a deny rule; use it only when that rule number is a rule specifically for port 22, because replacing a broad allow rule (such as rule 100 of the default network ACL) blocks all traffic it allowed.
aws ec2 replace-network-acl-entry --network-acl-id <NETWORK_ACL_ID> --ingress --rule-number <RULE_NUMBER> --protocol 6 --rule-action deny --cidr-block 0.0.0.0/0 --port-range From=22,To=22
In most cases, add deny rules for port 22 with a rule number lower than the allow rule instead (one for IPv4 and one for IPv6):
aws
ec2 create-network-acl-entry --network-acl-id <NETWORK_ACL_ID>
--ingress --rule-number <RULE_NUMBER> --protocol 6 --port-range
From=22,To=22 --cidr-block 0.0.0.0/0 --rule-action deny
aws ec2
create-network-acl-entry --network-acl-id <NETWORK_ACL_ID>
--ingress --rule-number <IPV6_RULE_NUMBER> --protocol 6
--port-range From=22,To=22 --ipv6-cidr-block ::/0 --rule-action deny
Backout Plan
Before making changes, save the current network ACL rules:
aws ec2 describe-network-acls --network-acl-ids <NETWORK_ACL_ID> > <NETWORK_ACL_ID>-backup.json
To roll back:
Sign in to the AWS Management Console at https://console.aws.amazon.com.
Open the Amazon VPC console, select Network ACLs and select the network ACL.
On the Inbound rules tab, choose Edit inbound rules.
Remove the deny rules that were added, and re-add any removed rules using the saved backup (rule number, type, port range, source and action).
Choose Save changes.
CLI:
aws ec2 delete-network-acl-entry --network-acl-id <NETWORK_ACL_ID> --ingress --rule-number <RULE_NUMBER>
Note: rolling back allows SSH from the Internet at the subnet level again. Use it only as a temporary measure.
Reference