Hardening security groups: the basics
What does "too open" actually mean across your firewall rules?
A security group is a stateful firewall wrapped around an ENI; each ingress rule answers one question, which source addresses may reach which port. "Too open" shows up in several shapes. SSH on port 22 from 0.0.0.0/0 (EC2.13) and RDP on 3389 (EC2.14) are administrative doors flung open to the whole internet. A network ACL that allows ingress to those same admin ports (EC2.21) is the subnet-level version of the same mistake. Beyond admin ports, any high-risk port open to the world fails EC2.19, and the stricter EC2.18 fails any port open to 0.0.0.0/0 that is not on an explicit authorised list (by default just 80 and 443). Data-tier services have their own checks: RDS.23 fails an RDS instance that does not use the default engine port and exposes it openly, and Redshift.15 fails a Redshift cluster whose security group allows unrestricted ingress.
Two more controls catch the rules nobody meant to leave behind. EC2.2 fails any VPC's default security group that still carries rules, because the only safe state for a default SG is empty, so anything that lands on it by accident gets no connectivity. These look like separate problems on the report, but they are one capability: only authorised sources should reach a port, and rules you did not deliberately open should not exist.
The good news is that almost none of this is intent. An open SSH rule is the residue of a 2am incident; an exposed Redis port on 6379 was opened for "just a quick test" and never closed; a default SG carries the stock rules AWS shipped and nobody audited. The job is to find every rule that opens a port to a source wider than it should be, scope it to the smallest source that works (or remove it entirely), strip the default groups, and then guardrail the whole thing so the rules cannot come back.
In this lesson you will learn how AWS expresses an over-open firewall across security groups, network ACLs and data-tier services, how to find every rule exposing a port to a source wider than it should be, and how to scope or remove them without locking yourself out. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.
Minutes to first attack, and a ransom wave nobody saw coming
Researchers who stand up fresh honeypots with SSH open to 0.0.0.0/0 consistently log the first automated brute-force attempt within minutes, sometimes under a minute, from compromised hosts all over the world. The whole IPv4 space is scanned continuously, so there is no "it is only a test box, nobody knows it is there." The data tier is just as exposed: in early 2017 a botnet swept the internet for Elasticsearch on port 9200 with no authentication, found roughly 35,000 clusters, wiped the indexes and left a ransom note. Port 9200 is not on any high-risk list, which is exactly why the stricter authorised-port control exists, to catch the long tail before a botnet does.
Closing over-open ingress across an estate
Marco is on the platform team and the posture dashboard surfaces a cluster of failures: open SSH on several security groups, an exposed Redis and Elasticsearch tier behind the internal API, and 47 default security groups carrying their stock rules across every region of a recently acquired account.
Rather than work them one by one, he starts by enumerating the worst offenders, the rules that open a port to 0.0.0.0/0, so he can separate residue from anything genuinely load-bearing before changing a thing.
List every ingress rule on a data-tier security group whose source is the whole internet. The JMESPath filter surfaces what the finding only summarises.
Every 0.0.0.0/0 rule on the group. The data-tier ports are the highest-value targets; fix those first.
How AWS decides a rule is too opendeep dive
Most of these controls are backed by an AWS Config managed rule that evaluates security-group or network-ACL rules. EC2.13 (restricted-ssh) and EC2.14 fail when port 22 or 3389 is open to 0.0.0.0/0 or ::/0; the IPv4 and IPv6 sources are independent objects, so revoking the visible 0.0.0.0/0 rule while an ::/0 rule remains keeps the finding open. EC2.21 applies the same logic at the network-ACL layer. EC2.19 checks a fixed list of high-risk ports, while EC2.18 inverts the question, failing any port open to the internet that is not on a customer-configured authorised list, and its compare is exact: a rule for ports 80 to 8080 fails because the range includes ports outside the list, even though it covers 80. RDS.23 checks that an RDS instance avoids the default engine port when openly reachable, and Redshift.15 checks for unrestricted ingress to a cluster's security group.
EC2.2 is the odd one: it fails any default security group that has any rule at all, with no "is the rule dangerous" logic, because the policy is that a default SG should be unusable by accident. A common trap across the group is a port range that merely contains a sensitive port, FromPort 20 to ToPort 23 trips the SSH check even though nobody wrote a rule for 22. Security Hub evaluates all of these through AWS Config on roughly a 12-hour cadence plus on change, so a fix clears at the next evaluation rather than instantly.
The durable fix is to stop needing the open rule. For administrative access, AWS Systems Manager Session Manager gives an authenticated, audited shell over the agent's outbound connection with no inbound rule, no public IP and no bastion. For everything else, scope the source to a specific security-group ID or a corporate VPN CIDR rather than a wide block, terminate genuinely public traffic on a load balancer, and back it all with a Config rule or a Service Control Policy that blocks the open rule from being created in the first place.
What is the impact of leaving security groups too open?
The primary impact is breach exposure. An open administrative port draws continuous credential-stuffing and brute-force traffic; an open, often unauthenticated data-tier port (Redis, Elasticsearch, a database) is an open door to the records behind it. A scraper pulling rows from an exposed cache does not trip alarms, it looks like normal client traffic, so exfiltration through one of these ports can run for months before anyone notices, by which point the data is on a paste site. Unlike a cost finding, the downside is not a predictable monthly number; it is a low-probability, very-high-severity event.
The second-order impact is the rules nobody is watching. A resource that inherits a populated default security group gets broad network access with no audit trail, which widens the blast radius of any incident: after a breach, "what could this resource reach?" answers "anywhere on the internet" instead of a bounded set. Stripping default groups and scoping every rule shrinks that surface to the doors you actually operate and monitor.
On the compliance side, these controls map directly to CIS AWS Foundations, PCI DSS network-segmentation requirements (1.2.1, 1.3.x), NIST 800-53 SC-7 boundary protection, and the remote-access question on every cyber-insurance renewal. A standing count of open-port findings is a write-up an auditor will make, an answer an insurer will penalise, and a question a security-conscious customer will ask during procurement. "Open" is an expensive word in all three contexts, and it costs nothing to close.
How do you harden security groups safely?
Work the capability as one loop rather than chasing individual findings. The order matters: confirm what actually depends on a rule before you revoke it, so you do not lock yourself out or sever a forgotten production resource.
1. Inventory every over-open rule, IPv4 and IPv6
List every security-group rule and network-ACL entry that opens a port to a source wider than it should be: admin ports (22, 3389) to 0.0.0.0/0 or ::/0, high-risk and non-authorised ports to the internet, openly reachable database and Redshift ports, and any rule on a default security group. Run the audit for IPv4 and IPv6 separately, watch for ranges that merely contain a sensitive port, and capture the exact rule for each so you have a precise rollback.
2. Confirm what depends on each rule before you touch it
Check whether anything actually uses the open access: CI jobs, deployment tooling, a legacy bastion, humans connecting directly, or an orphaned ENI sitting on a default group. If instances run the SSM agent and the team already uses Session Manager, an open SSH rule is usually pure residue and safe to remove. For default groups, run describe-network-interfaces first, an ENI whose only attachment is the default SG must be reassigned before you strip the rules.
3. Scope the source down, or remove it entirely
Revoke the over-open rule and replace it with the smallest scope that works: a specific security-group reference (which survives subnet renumbering and Auto Scaling), a peer VPC CIDR, a load balancer's security group, or a corporate VPN CIDR. For administrative access, prefer no inbound rule at all via Session Manager. For genuinely public services, terminate on an ALB or NLB rather than a raw rule. Strip every default security group to empty. If a port legitimately must be public, add it to the EC2.18 authorised list after a documented review, never silently to clear a finding.
4. Add a guardrail so the rules cannot come back
This capability's defining trait is recurrence. Prevent it at creation: enable the backing AWS Config rules as detective controls, and add Service Control Policies that deny authorize-security-group-ingress for the open patterns (port 22 or 3389 from 0.0.0.0/0, rules on default groups). With the guardrail in place the next 2am incident rule simply cannot be created, and the count stays at zero without manual policing.
# 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" Quick quiz
Question 1 of 5Security Hub shows open SSH, an exposed Redis tier, and dozens of populated default security groups. What is the most efficient way to think about them?
You scored
0 / 5
Keep learning
Go deeper on how security groups, network ACLs and the tooling that removes the need for open ingress work.
- Working with security group rules (Amazon EC2) How to view, add and revoke inbound rules, the mechanics behind scoping or removing an over-open rule.
- AWS Config managed rule: restricted-ssh The backing rule that evaluates port 22 ingress, including how IPv4 and IPv6 sources and port ranges are checked.
- AWS Systems Manager Session Manager Authenticated, audited shell access with no inbound port, the durable way to retire internet-facing admin access.
You can now treat security-group hardening as one capability rather than a scatter of findings: inventory every over-open rule across both IP families, confirm what depends on it, scope it to the smallest source that works (or remove it via Session Manager), strip the default groups, and ratchet the estate shut with Config rules and Service Control Policies so the open rules cannot return. The Controls this lesson covers section below links every control in this group to its deep page and fix.
Back to the library