Ensure No EC2 Instances Allow Ingress from the Internet to TCP Port 22 (SSH)
Overview
This check identifies EC2 instances that have SSH (TCP port 22) exposed to the entire internet through their security group rules. When a security group allows inbound traffic from 0.0.0.0/0 (all IPv4 addresses) or ::/0 (all IPv6 addresses) on port 22, the instance becomes accessible to anyone on the internet.
Severity: Critical
Risk
SSH is one of the most commonly targeted services by attackers. When exposed to the internet:
- Brute force attacks: Automated bots constantly scan the internet for open SSH ports and attempt to guess credentials
- Credential stuffing: Attackers use stolen username/password combinations from data breaches
- Remote shell access: A successful compromise gives attackers full command-line access to your server
- Lateral movement: Once inside, attackers can move to other resources in your VPC
- Data exfiltration: Sensitive data can be stolen from the compromised instance
Remediation Steps
Prerequisites
You need permission to modify EC2 security groups (the ec2:RevokeSecurityGroupIngress and ec2:AuthorizeSecurityGroupIngress permissions).
AWS Console Method
- Sign in to the AWS Console and go to EC2
- In the left navigation, click Instances
- Select the flagged instance and note the Security groups in the details panel
- Click the security group name to open it
- Select the Inbound rules tab and click Edit inbound rules
- Find any SSH rules (port 22) with Source set to
0.0.0.0/0or::/0 - Either:
- Delete the rule if SSH access is not needed, or
- Change the Source to a specific IP address or CIDR range (e.g., your office IP
203.0.113.50/32)
- Click Save rules
AWS CLI (optional)
Remove the internet-exposed SSH rule:
# Remove IPv4 rule allowing SSH from anywhere
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
# Remove IPv6 rule allowing SSH from anywhere (if present)
aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--ip-permissions '[{"IpProtocol":"tcp","FromPort":22,"ToPort":22,"Ipv6Ranges":[{"CidrIpv6":"::/0"}]}]'
Add a restricted SSH rule (replace with your trusted IP):
aws ec2 authorize-security-group-ingress \
--region us-east-1 \
--group-id <SECURITY_GROUP_ID> \
--protocol tcp \
--port 22 \
--cidr 203.0.113.50/32
Replace:
<SECURITY_GROUP_ID>with your security group ID (e.g.,sg-0123456789abcdef0)203.0.113.50/32with your trusted IP address or CIDR range
CloudFormation (optional)
Use this template to create a security group with SSH restricted to a specific IP:
AWSTemplateFormatVersion: '2010-09-09'
Description: Security group with restricted SSH access
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the security group will be created
TrustedSshCidr:
Type: String
Description: CIDR block allowed to SSH (e.g., your office IP)
Default: 203.0.113.50/32
AllowedPattern: ^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$
Resources:
RestrictedSshSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group with SSH restricted to trusted IPs
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref TrustedSshCidr
Description: SSH from trusted IP only
Tags:
- Key: Name
Value: restricted-ssh-sg
Outputs:
SecurityGroupId:
Description: ID of the security group
Value: !Ref RestrictedSshSecurityGroup
Deploy the template:
aws cloudformation create-stack \
--region us-east-1 \
--stack-name restricted-ssh-sg \
--template-body file://restricted-ssh-sg.yaml \
--parameters \
ParameterKey=VpcId,ParameterValue=<YOUR_VPC_ID> \
ParameterKey=TrustedSshCidr,ParameterValue=<YOUR_IP>/32
Terraform (optional)
variable "vpc_id" {
description = "VPC ID where the security group will be created"
type = string
}
variable "trusted_ssh_cidr" {
description = "CIDR block allowed to SSH (e.g., your office IP)"
type = string
default = "203.0.113.50/32"
}
resource "aws_security_group" "restricted_ssh" {
name = "restricted-ssh-sg"
description = "Security group with SSH restricted to trusted IPs"
vpc_id = var.vpc_id
ingress {
description = "SSH from trusted IP only"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.trusted_ssh_cidr]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "restricted-ssh-sg"
}
}
output "security_group_id" {
description = "ID of the security group"
value = aws_security_group.restricted_ssh.id
}
Apply the configuration:
terraform init
terraform apply -var="vpc_id=<YOUR_VPC_ID>" -var="trusted_ssh_cidr=<YOUR_IP>/32"
Verification
After making changes:
- Go to EC2 > Security Groups in the AWS Console
- Select the modified security group
- Check the Inbound rules tab
- Confirm there are no SSH rules (port 22) with Source
0.0.0.0/0or::/0
CLI verification
# List inbound rules for the security group
aws ec2 describe-security-groups \
--region us-east-1 \
--group-ids <SECURITY_GROUP_ID> \
--query 'SecurityGroups[0].IpPermissions[?FromPort==`22`]'
The output should show no rules with 0.0.0.0/0 or ::/0 in the CidrIp or CidrIpv6 fields.
Additional Resources
- AWS Security Groups Documentation
- AWS Systems Manager Session Manager - SSH alternative that does not require open inbound ports
- EC2 Instance Connect - Browser-based SSH without persistent open ports
Notes
- Better alternative: Consider using AWS Systems Manager Session Manager instead of SSH. It provides secure shell access without requiring any inbound ports to be open.
- Bastion hosts: If you must use SSH, consider routing access through a hardened bastion host in a private subnet, accessible only via VPN.
- Key-based authentication: Always use SSH key pairs instead of passwords. Disable password authentication in your SSH configuration.
- Multiple security groups: An EC2 instance can have multiple security groups. Check all of them for overly permissive SSH rules.
- Dynamic IPs: If your IP address changes frequently, consider using AWS Systems Manager Session Manager or EC2 Instance Connect instead of maintaining IP-based allow lists.