Back to blog
AWSBest PracticesCloud SecurityNetworkingOperations & Compliance

Default Security Group In Use: Why It Matters and How to Fix It

Learn why attaching resources to the AWS default security group is risky, how to move them to purpose-built groups, and how to prevent it with policy-as-code.

TL;DR

This check flags any network interface still attached to a VPC's default security group. Default groups carry permissive rules and ambiguous ownership, which makes them a poor fit for production. Move your resources to a purpose-built security group and lock the default down so nothing can use it.

Every VPC you create in AWS comes with a default security group. It exists so that instances launched without an explicit group still get one. Convenient, sure, but that convenience is exactly the problem. The default security group becomes a catch-all that nobody owns, nobody reviews, and nobody can confidently change without worrying about what else depends on it.

The ec2_defaultsg check looks for any elastic network interface (ENI) that is currently associated with a default security group. If something is using it, the check fails.


What this check detects

The check inspects the security groups attached to your ENIs and identifies any that are the default group for their VPC. A default security group is easy to spot: its GroupName is literally default, and it cannot be deleted.

An ENI can pick up the default group in several ways:

  • An EC2 instance launched without specifying a security group
  • An RDS instance, Lambda function, or load balancer created with default networking settings
  • A VPC endpoint or NAT-adjacent resource provisioned through a quick console wizard
  • Terraform or CloudFormation that omits the vpc_security_group_ids argument

Note: The default security group has a special rule out of the box: it allows all inbound traffic from itself. In other words, any resource in the default group can talk to any other resource in the same group on all ports. That implicit trust mesh grows quietly as more resources land in the group.


Why it matters

The default security group looks harmless because its default inbound rule only allows traffic from members of the same group. The risk is not the rule itself, it is how the group gets used over time.

1. It becomes an unaudited trust zone

Because the default group allows all traffic between its own members, every resource you accidentally drop into it can reach every other resource on every port. A compromised web server in the default group can pivot straight to a database in the default group with no extra rules blocking it. This is the kind of lateral movement that turns a single foothold into a full breach.

2. Nobody owns it

Purpose-built security groups have a name and usually a tag that tells you what they protect: web-tier-sg, rds-postgres-sg, and so on. The default group protects nothing in particular and everything by accident. When an engineer needs to tighten a rule, they hesitate because they cannot tell what will break.

3. It drifts toward permissive

Default groups are a favorite target for "just make it work" changes. Someone adds an inbound rule for 0.0.0.0/0 on port 22 to debug something, forgets to remove it, and now every resource in the group is exposed to the internet. Because the group is shared, that one change widens the blast radius across everything attached to it.

Warning: Several compliance frameworks, including CIS AWS Foundations Benchmark (control 5.4 / 5.3 depending on version), require that default security groups restrict all traffic. Leaving resources attached to the default group makes that control impossible to satisfy cleanly.


How to fix it

Fixing this is a two-part job. First, move resources off the default group onto a proper one. Second, lock the default group down so nothing can use it usefully again.

Step 1: Find what is using the default group

List the default security groups across your account:

aws ec2 describe-security-groups \
  --filters Name=group-name,Values=default \
  --query 'SecurityGroups[].{ID:GroupId,VPC:VpcId}' \
  --output table

Then find the network interfaces attached to a given default group:

aws ec2 describe-network-interfaces \
  --filters Name=group-id,Values=sg-0abc123default \
  --query 'NetworkInterfaces[].{ENI:NetworkInterfaceId,Type:InterfaceType,Desc:Description,Attached:Attachment.InstanceId}' \
  --output table

This tells you exactly which instances, load balancers, RDS interfaces, or Lambda ENIs depend on the default group.

Step 2: Create a purpose-built security group

aws ec2 create-security-group \
  --group-name web-tier-sg \
  --description "Inbound 443 from ALB, outbound to app tier" \
  --vpc-id vpc-0abc1234

Add only the rules the workload actually needs:

aws ec2 authorize-security-group-ingress \
  --group-id sg-0newwebsg \
  --protocol tcp \
  --port 443 \
  --source-group sg-0albgroup

Step 3: Reassign the network interface

For an EC2 instance or any standalone ENI, replace the group set on the interface. Note that --groups replaces the full list, so include every group the ENI should keep.

Danger: Changing the security groups on a live ENI takes effect immediately. If your new group is missing a rule the workload relies on, you will drop connections the moment you run this. Verify the new group's rules against current traffic before swapping.

aws ec2 modify-network-interface-attribute \
  --network-interface-id eni-0abc123 \
  --groups sg-0newwebsg

For managed services, change the group through that service's API rather than the ENI directly. For example, RDS:

aws rds modify-db-instance \
  --db-instance-identifier my-postgres \
  --vpc-security-group-ids sg-0rdsgroup \
  --apply-immediately

Step 4: Lock down the default group

You cannot delete a default security group, but you can strip it of all rules so it grants nothing. Remove the self-referencing inbound rule:

aws ec2 revoke-security-group-ingress \
  --group-id sg-0abc123default \
  --ip-permissions '[{"IpProtocol":"-1","UserIdGroupPairs":[{"GroupId":"sg-0abc123default"}]}]'

And the default allow-all egress rule:

aws ec2 revoke-security-group-egress \
  --group-id sg-0abc123default \
  --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'

With no rules, the default group denies everything. Anything that accidentally lands in it later will simply have no connectivity, which is a loud, obvious failure rather than a silent security gap.

Tip: Do the lock-down step in every VPC, including the default VPC in every region, even regions you do not actively use. Empty default groups in unused regions are a common blind spot.


How to prevent it from happening again

Manual cleanup only holds until the next quick console launch. Bake the prevention into your provisioning and your pipelines.

Always specify a security group in IaC

The most common cause is omitting the group argument. In Terraform, always set it explicitly:

resource "aws_instance" "web" {
  ami                    = "ami-0abc1234"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.private.id
  vpc_security_group_ids = [aws_security_group.web_tier.id]
}

resource "aws_security_group" "web_tier" {
  name        = "web-tier-sg"
  description = "Inbound 443 from ALB"
  vpc_id      = aws_vpc.main.id
}

Manage the default group as code

Use the dedicated Terraform resource to keep every default group empty. This makes the locked-down state permanent and reviewable:

resource "aws_default_security_group" "default" {
  vpc_id = aws_vpc.main.id
  # No ingress or egress blocks = deny all
}

Add a policy-as-code gate

Catch violations before they merge. A Checkov / OPA style rule that fails any resource referencing a default group, or a Conftest policy in CI, stops the problem at the pull request. For runtime enforcement, an AWS Config rule does the same after the fact:

aws configservice put-config-rule --config-rule '{
  "ConfigRuleName": "default-sg-must-restrict-traffic",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "VPC_DEFAULT_SECURITY_GROUP_CLOSED"
  }
}'

Tip: Lensix runs the ec2_defaultsg check continuously, so you get told the moment a resource attaches to a default group, instead of finding out during an audit. Pair it with the AWS Config rule above for both real-time detection and a compliance trail.


Best practices

  • One group, one job. Name and tag every security group after the workload it protects. Ambiguous groups invite ambiguous rules.
  • Reference groups, not CIDRs, for internal traffic. Use source-group rules between tiers so access follows identity rather than IP ranges that drift.
  • Keep default groups empty everywhere. Treat an empty default group as the baseline for every VPC in every region.
  • Avoid the default VPC entirely. Build workloads in purpose-created VPCs with explicit subnets and groups. The default VPC encourages exactly the shortcuts this check exists to catch.
  • Review group membership regularly. Audit which ENIs use which groups so you can spot creep before it becomes a sprawling trust mesh.

The default security group is not a bug, it is a convenience that ages badly. Move your resources onto groups with clear ownership, empty out the defaults, and gate the whole thing in CI. After that, the only thing the default group does is sit there denying traffic, which is exactly what you want from a group nobody is supposed to use.