Back to blog
AWSBest PracticesCloud SecurityNetworking

Network ACL Allows Unrestricted Outbound: Why Open Egress Is a Risk

Learn why AWS Network ACLs that allow unrestricted outbound traffic are a security risk, plus step-by-step CLI, Terraform, and CI fixes to scope egress safely.

TL;DR

This check flags Network ACLs that permit all outbound traffic (0.0.0.0/0 on every port and protocol). Unrestricted egress lets a compromised instance exfiltrate data or reach attacker-controlled endpoints. Replace the wide-open allow rule with scoped egress rules that only permit the destinations and ports your workloads actually need.

Most teams put a lot of effort into locking down inbound traffic. Egress, on the other hand, usually gets left wide open because "it's just our stuff talking out to the internet." That assumption is exactly what attackers count on. When a Network ACL allows unrestricted outbound traffic, you have removed one of the layers that could slow down or block data exfiltration, command-and-control callbacks, and lateral movement to external services.

This Lensix check, vpc_nacloutbound in the vpc_checks module, looks for AWS Network ACLs with an outbound rule that allows all traffic to 0.0.0.0/0. Below we cover what it catches, why it is worth your attention, and how to fix it without breaking your workloads.


What this check detects

A Network ACL (NACL) is a stateless firewall that operates at the subnet boundary in a VPC. Every subnet is associated with exactly one NACL, and that NACL has two ordered rule sets: inbound and outbound. Rules are evaluated by rule number, lowest first, and the first match wins.

This check fires when a NACL has an outbound entry that looks like this:

{
  "RuleNumber": 100,
  "Protocol": "-1",
  "RuleAction": "allow",
  "Egress": true,
  "CidrBlock": "0.0.0.0/0",
  "PortRange": null
}

Protocol -1 means all protocols, 0.0.0.0/0 means any destination, and the absence of a port range means every port. In plain terms, anything in the subnet can send traffic anywhere, on any port, using any protocol.

Note: The default NACL that AWS creates with every VPC ships with exactly this rule (rule 100, allow all egress). So if you have never touched your NACLs, this check will almost certainly flag them. That is by design on AWS's part, but it is not a posture you want to keep in sensitive subnets.


Why it matters

NACLs are a defense-in-depth control. They are not your only line of defense (security groups handle most instance-level filtering), but they are the one layer that applies to an entire subnet regardless of how individual instances are configured. Leaving egress completely open weakens that layer.

Data exfiltration

If an attacker compromises an instance, an open egress path is what lets them ship your data off-network. They can push database dumps to an S3 bucket they control, tunnel data over DNS, or POST it to a remote endpoint. Restricting outbound traffic at the subnet level forces that traffic through known, monitored paths and makes exfiltration noisier and harder.

Command-and-control and malware callbacks

Most modern malware needs to call home to receive instructions or download a second-stage payload. Unrestricted egress means that callback succeeds on the first try. Scoping outbound traffic to a known set of destinations breaks that initial connection for anything trying to reach an arbitrary internet host.

Crypto mining and abuse

Compromised compute is frequently turned into a mining node, which needs to reach a mining pool over specific ports. Egress filtering at the subnet boundary can stop that traffic cold, and the failed connections become a useful signal in your flow logs.

Warning: NACLs are stateless. Unlike security groups, they do not automatically allow return traffic. If you tighten outbound rules, you must also allow inbound traffic on the ephemeral port range (typically 1024 to 65535) for responses, or legitimate connections will hang.


How to fix it

The goal is to replace the blanket allow-all egress rule with rules that permit only the destinations and ports your subnet needs. The exact rules depend on the subnet's role, so start by figuring out what actually leaves it.

Step 1: Identify the NACL and its associations

aws ec2 describe-network-acls \
  --query 'NetworkAcls[].{Id:NetworkAclId,Default:IsDefault,Subnets:Associations[].SubnetId}' \
  --output table

Look at which subnets the flagged NACL is attached to so you understand what you are about to change.

Step 2: Understand current egress patterns

Before tightening anything, check VPC Flow Logs to see where traffic actually goes. If you do not have flow logs enabled, enable them first and let them collect for a representative period.

aws ec2 create-flow-logs \
  --resource-type Subnet \
  --resource-ids subnet-0abc123def456 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::111122223333:role/flowlogsRole

Step 3: Add scoped outbound rules

For most application subnets, outbound traffic falls into a handful of categories: HTTPS to APIs and package repos, HTTP for some redirects and metadata, DNS, and NTP. Here is a typical set of egress rules for a private subnet that needs internet access through a NAT gateway:

# Allow HTTPS out
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456 \
  --rule-number 100 --egress \
  --protocol tcp --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 --rule-action allow

# Allow HTTP out
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456 \
  --rule-number 110 --egress \
  --protocol tcp --port-range From=80,To=80 \
  --cidr-block 0.0.0.0/0 --rule-action allow

# Allow DNS over UDP
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456 \
  --rule-number 120 --egress \
  --protocol udp --port-range From=53,To=53 \
  --cidr-block 0.0.0.0/0 --rule-action allow

Because NACLs are stateless, add a matching inbound rule for ephemeral ports so return traffic gets back in:

aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456 \
  --rule-number 100 \
  --protocol tcp --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 --rule-action allow

Step 4: Remove the allow-all egress rule

Danger: Deleting the broad egress rule before your scoped rules are in place and verified will cut off outbound connectivity for every instance in the subnet. Add and test the narrow rules first, then remove the catch-all. Do this in a maintenance window or on a non-production subnet before touching production.

aws ec2 delete-network-acl-entry \
  --network-acl-id acl-0abc123def456 \
  --rule-number 100 --egress

Terraform example

If you manage infrastructure as code, define the NACL with explicit egress rules instead of relying on the default:

resource "aws_network_acl" "app" {
  vpc_id     = aws_vpc.main.id
  subnet_ids = [aws_subnet.app.id]

  egress {
    rule_no    = 100
    protocol   = "tcp"
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 443
    to_port    = 443
  }

  egress {
    rule_no    = 110
    protocol   = "udp"
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 53
    to_port    = 53
  }

  ingress {
    rule_no    = 100
    protocol   = "tcp"
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 1024
    to_port    = 65535
  }

  tags = {
    Name = "app-subnet-nacl"
  }
}

Tip: Keep your NACL rules in version control alongside the rest of your network config. When egress needs change, the diff in your pull request becomes a record of who opened what and why, which is far easier to audit than clicking through the console months later.


How to prevent it from happening again

Fixing a single NACL is reactive. The point is to keep the default open egress rule from sneaking back into new VPCs and subnets.

Scan infrastructure as code in CI

Run a policy scanner against your Terraform or CloudFormation before it merges. Tools like Checkov, tfsec, or OPA/Conftest can fail a build when a NACL allows unrestricted egress. A Conftest rule looks like this:

package main

deny[msg] {
  resource := input.resource.aws_network_acl[name]
  egress := resource.egress[_]
  egress.action == "allow"
  egress.cidr_block == "0.0.0.0/0"
  egress.protocol == "-1"
  msg := sprintf("NACL %s allows unrestricted outbound traffic", [name])
}

Continuous detection

IaC scanning only covers resources you provision through IaC. Manual changes and drift still happen. Continuous scanning with Lensix catches NACLs that drift to an open egress state after deployment, so a quick console fix made during an incident does not quietly become permanent.

Use Service Control Policies as a backstop

If you run AWS Organizations, you cannot directly block NACL rule content with an SCP, but you can restrict who is allowed to modify NACLs at all. Limiting ec2:CreateNetworkAclEntry and ec2:ReplaceNetworkAclEntry to a network admin role reduces the number of people who can accidentally widen egress.


Best practices

  • Treat egress as a security boundary, not an afterthought. Default-deny outbound and explicitly allow what each subnet needs. The exercise of listing those destinations often surfaces dependencies you did not know you had.
  • Layer NACLs with security groups. Security groups do the fine-grained, stateful filtering per instance. NACLs give you a coarse, stateless backstop at the subnet edge. Use both; do not rely on one.
  • Segment subnets by trust level. A subnet hosting a database tier rarely needs to reach the open internet at all. A subnet of build agents pulling packages needs HTTPS egress but little else. Different roles deserve different egress rules.
  • Enable VPC Flow Logs everywhere. You cannot scope egress safely if you do not know what traffic leaves your subnets, and flow logs turn denied connections into useful alerts.
  • Document the ephemeral port requirement. The single most common reason a tightened NACL breaks things is forgetting that stateless rules need explicit inbound allowances for return traffic. Make it part of your NACL template.

Open inbound rules get caught in review because everyone is watching for them. Open egress slips through because it feels harmless. It is not. Egress is the path data takes on its way out the door.

Tightening outbound NACLs takes some upfront work to map your real traffic, but the payoff is a meaningful reduction in what a compromised instance can do. Start with your most sensitive subnets, validate with flow logs, and bake the scoped rules into your IaC so the wide-open default never comes back.

Fix AWS Network ACL Unrestricted Outbound Traffic | Lensix