Skip to main content
emnode / learn
Compliance Medium severity

AWS Security Hub · EC2

EC2.21: NACLs should not allow ingress to ports 22/3389

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub EC2.21 check?

EC2.21 fails when a non-default Network ACL has an inbound allow entry with a source of 0.0.0.0/0 (or ::/0) covering TCP port 22 (SSH) or 3389 (RDP). The default NACL is deliberately exempt — it is permissive by design and meant to be backstopped by security groups.

Why does EC2.21 matter?

A NACL wraps an entire subnet, so one over-broad rule exposes every instance behind it at once — you can have fifty perfectly locked-down security groups and still be wide open because the NACL above them waves the traffic through. Internet-facing SSH and RDP are the single most-scanned attack surface on the public cloud; bots sweep the IPv4 space for open 22 and 3389 within minutes of exposure.

How do I fix EC2.21?

  1. Pull the NACL's inbound entries and find any allow rule with 0.0.0.0/0 or ::/0 covering port 22 or 3389, noting the rule number.
  2. Confirm intent via IaC history, CloudTrail, and flow logs for recent accepted connections on those ports before removing it.
  3. Delete the offending entry by number with delete-network-acl-entry — the implicit final deny then blocks the traffic — or scope it to a known CIDR as a stopgap.
  4. Adopt SSM Session Manager for audited, identity-based access so no inbound admin ports are needed at all.

Remediation script · bash

# Revoke an over-open admin rule, covering both IPv4 and IPv6 in one call.
aws ec2 revoke-security-group-ingress --group-id sg-0a1b2c3d \
  --ip-permissions 'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=0.0.0.0/0}],Ipv6Ranges=[{CidrIpv6=::/0}]'

# Where access is genuinely needed, re-add it scoped to a source security group, not a CIDR.
aws ec2 authorize-security-group-ingress --group-id sg-0a1b2c3d \
  --ip-permissions 'IpProtocol=tcp,FromPort=6379,ToPort=6379,UserIdGroupPairs=[{GroupId=sg-0app1234,Description=app-tier}]'

# Strip a default security group to empty by feeding its current rules back into revoke.
INGRESS=$(aws ec2 describe-security-groups --group-ids sg-0default01 \
  --query 'SecurityGroups[0].IpPermissions')
[ "$INGRESS" != "[]" ] && aws ec2 revoke-security-group-ingress \
  --group-id sg-0default01 --ip-permissions "$INGRESS"

Full walkthrough (console steps, edge cases and verification) in the lesson Harden security groups and restrict ingress.

Is EC2.21 a false positive?

NACLs are stateless, so layering a deny rule on top rarely works: lowest-number-first wins, so a broad allow at rule 100 shadows a deny at rule 200. The clean fix is to delete the broad allow, not to add a deny above it.

Part of the learning path Lock down access
  • EC2.1 An EBS snapshot is publicly restorable by any account
  • EC2.2 Default security groups still allow traffic
  • EC2.3 Attached EBS volumes are not encrypted at rest
  • EC2.4 Long-stopped instances are abandoned attack surface
  • EC2.6 No VPC flow logs, so there is no network audit trail
  • EC2.7 New EBS volumes are not encrypted by default
  • EC2.8 IMDSv1 lets an SSRF steal instance credentials
  • EC2.9 Instances are directly reachable on public IPv4
  • EC2.10 EC2 API traffic leaves the VPC over the internet
  • EC2.13 SSH (port 22) is open to the entire internet
  • EC2.14 RDP (port 3389) is open to the entire internet
  • EC2.15 Subnets auto-assign public IPs to new instances