Why EC2 Security Failures Are Catastrophic
A single compromised EC2 instance can lead to complete infrastructure takeover. Attackers use compromised instances to access S3 buckets, launch additional resources, steal credentials, and deploy ransomware across your entire environment. The vast majority of successful EC2 attacks exploit known vulnerabilities in unpatched software.
The Six Most Dangerous EC2 Attack Vectors
Unpatched Software Vulnerabilities
The vast majority of successful EC2 attacks exploit known vulnerabilities in unpatched operating systems, web servers, and applications with public CVEs.
Overprivileged IAM Roles
Instance metadata attacks steal IAM credentials from compromised instances. Overprivileged roles allow attackers to access additional AWS resources and escalate privileges.
Weak Access Controls
SSH key compromises, weak passwords, default credentials, shared keys, and overly permissive security groups create multiple attack pathways.
Network Exposure
Public IP addresses with open ports expose instances to internet-wide attacks. Unrestricted security groups and missing network segmentation enable lateral movement.
Unencrypted Data at Rest
Unencrypted EBS volumes and snapshots expose sensitive data. Missing encryption, shared snapshots, and inadequate backup security provide data access to attackers.
Insufficient Monitoring
Blind spot exploitation allows attacks to persist undetected. Missing logging, no intrusion detection, and inadequate alerting enable long-term compromise.
The Four Critical Business Risks
- Ransomware & Data Encryption: Direct file encryption, network propagation through shared credentials, cloud resource hijacking, and backup destruction
- Cryptocurrency Mining: Massive unexpected AWS bills ($50,000+ monthly), performance degradation, botnet participation
- Data Exfiltration: Database credentials in config files, S3 access through overprivileged roles, API keys in environment variables
- Compliance Violations: PCI DSS Requirement 6.1 (patching), HIPAA Β§164.308(a)(5) (endpoint security), SOC 2 CC6.8 (vulnerability management)
Implement Security Groups Defense-in-Depth
~12 minutes
Security groups are your first line of defense against network-based attacks. Properly configured security groups can prevent the majority of common EC2 compromises by blocking unauthorized access at the network layer.
Prerequisites
- List of all EC2 instances and their required network access
- Understanding of application architecture and data flows
- Administrative access to EC2 and VPC services
Console Steps
1.1 Audit Current Security Groups
- Navigate to
EC2 Console β Security Groups - Review all security groups for overly permissive rules
- Look for rules allowing
0.0.0.0/0access on sensitive ports - Identify unused or default security groups
# Find security groups with overly permissive rules
aws ec2 describe-security-groups \
--query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' \
--output table
# Find security groups allowing SSH from anywhere
aws ec2 describe-security-groups \
--query 'SecurityGroups[?IpPermissions[?FromPort==`22` && IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' \
--output table
# Find security groups allowing RDP from anywhere
aws ec2 describe-security-groups \
--query 'SecurityGroups[?IpPermissions[?FromPort==`3389` && IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' \
--output table
1.2 Create Layered Security Groups
Create specific security groups for each application tier:
web-tier-sg- Web servers (ports 80, 443 only)app-tier-sg- Application servers (specific app ports)db-tier-sg- Database servers (database ports only)admin-access-sg- Administrative access (SSH/RDP from specific IPs)
# Create web tier security group
aws ec2 create-security-group \
--group-name web-tier-sg \
--description "Web tier security group - HTTP/HTTPS only" \
--vpc-id vpc-12345678
# Allow HTTP and HTTPS from anywhere (for public web servers)
aws ec2 authorize-security-group-ingress \
--group-id sg-web-tier-id \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-web-tier-id \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Create admin access security group (restrict to office IPs)
aws ec2 create-security-group \
--group-name admin-access-sg \
--description "Administrative access - SSH/RDP from office only" \
--vpc-id vpc-12345678
# Allow SSH from office IP range only
aws ec2 authorize-security-group-ingress \
--group-id sg-admin-id \
--protocol tcp \
--port 22 \
--cidr 203.0.113.0/24
1.3 Configure Security Group Chaining
Use security group references instead of IP addresses for internal communication:
# Allow app tier to access database tier (PostgreSQL)
aws ec2 authorize-security-group-ingress \
--group-id sg-db-tier-id \
--protocol tcp \
--port 5432 \
--source-group sg-app-tier-id
# Allow web tier to access app tier
aws ec2 authorize-security-group-ingress \
--group-id sg-app-tier-id \
--protocol tcp \
--port 8080 \
--source-group sg-web-tier-id
Enable IMDSv2 & Automated Patching
~10 minutes
Instance Metadata Service v2 (IMDSv2) prevents Server-Side Request Forgery (SSRF) attacks that steal IAM credentials. Automated patching through Systems Manager prevents exploitation of known vulnerabilities.
Enable IMDSv2
2.1 Enable IMDSv2 for New Instances
- In EC2 Launch Template or Launch Instance wizard
- Expand
Advanced detailssection - Set
Metadata versionto "V2 only (token required)" - Set
Metadata response hop limitto 1
2.2 Update Existing Instances
- Select existing EC2 instances in console
Actions β Instance settings β Modify instance metadata options- Set
Metadata versionto "V2 only" - Set
Metadata response hop limitto 1 - Click
Save
# Enable IMDSv2 for specific instance
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1
# Bulk update all running instances
for instance in $(aws ec2 describe-instances \
--query 'Reservations[*].Instances[?State.Name==`running`].InstanceId' \
--output text); do
echo "Updating instance: $instance"
aws ec2 modify-instance-metadata-options \
--instance-id $instance \
--http-tokens required \
--http-put-response-hop-limit 1
done
# Test IMDSv2 from within instance
# Get token (required for IMDSv2)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Use token to access metadata
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id
Configure Automated Patching
2.3 Ensure SSM Agent is Installed
Modern Amazon Linux 2/2023 and Windows AMIs include SSM Agent. For older instances:
# Install SSM Agent on Amazon Linux 2
sudo yum install -y amazon-ssm-agent
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
# Install SSM Agent on Ubuntu
sudo snap install amazon-ssm-agent --classic
sudo systemctl enable snap.amazon-ssm-agent.amazon-ssm-agent.service
sudo systemctl start snap.amazon-ssm-agent.amazon-ssm-agent.service
# Create IAM role for SSM
aws iam create-role \
--role-name EC2-SSM-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach managed policy
aws iam attach-role-policy \
--role-name EC2-SSM-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
2.4 Create Maintenance Window for Patching
- Navigate to
Systems Manager β Maintenance Windows - Click
Create maintenance window - Schedule: Weekly during low usage periods (e.g., Sundays 2 AM UTC)
- Duration: 2-4 hours depending on environment size
- Use instance tags to group similar systems
# Create maintenance window
aws ssm create-maintenance-window \
--name "WeeklyPatchingWindow" \
--description "Weekly patching for production instances" \
--duration 4 \
--cutoff 1 \
--schedule "cron(0 2 ? * SUN *)" \
--schedule-timezone "UTC" \
--allow-unassociated-targets
# Tag instances for patch groups
aws ec2 create-tags \
--resources i-1234567890abcdef0 \
--tags Key=PatchGroup,Value=Production
# Check patch compliance
aws ssm describe-instance-patch-states \
--instance-ids i-1234567890abcdef0
Configure Encryption & Network Segmentation
~10 minutes
EBS encryption protects data at rest, while proper network segmentation contains security breaches and prevents lateral movement by attackers.
Enable EBS Encryption
3.1 Enable EBS Encryption by Default
- Navigate to
EC2 β Account attributes β EBS encryption - Click
Manageand enable "Always encrypt new EBS volumes" - Select default encryption key (AWS managed or customer managed)
- Apply to all regions where you operate
# Enable EBS encryption by default
aws ec2 enable-ebs-encryption-by-default
# Optionally set a custom KMS key
aws ec2 modify-ebs-default-kms-key-id \
--kms-key-id arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
# Check encryption status
aws ec2 get-ebs-encryption-by-default
# Find unencrypted volumes
aws ec2 describe-volumes \
--query 'Volumes[?Encrypted==`false`].[VolumeId,State,Attachments[0].InstanceId]' \
--output table
3.2 Encrypt Existing Volumes
For existing unencrypted volumes:
- Create snapshots of unencrypted volumes
- Copy snapshots with encryption enabled
- Create new encrypted volumes from encrypted snapshots
- Replace unencrypted volumes during maintenance windows
Implement Network Segmentation
3.3 Create Isolated Subnets
- Create public subnets only for load balancers and NAT gateways
- Place application servers in private subnets
- Isolate databases in dedicated private subnets
- Use separate subnets per availability zone for high availability
# Create private subnet for application tier
aws ec2 create-subnet \
--vpc-id vpc-12345678 \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=app-private-1a}]'
# Create private subnet for database tier
aws ec2 create-subnet \
--vpc-id vpc-12345678 \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=db-private-1a}]'
3.4 Configure Network ACLs
Add Network ACLs as an additional layer beyond security groups:
# Create restrictive NACL for database subnet
aws ec2 create-network-acl \
--vpc-id vpc-12345678 \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=db-tier-nacl}]'
# Allow inbound database traffic only from app tier
aws ec2 create-network-acl-entry \
--network-acl-id acl-db123456 \
--rule-number 100 \
--protocol tcp \
--rule-action allow \
--ingress \
--port-range From=5432,To=5432 \
--cidr-block 10.0.1.0/24
# Deny all other inbound traffic
aws ec2 create-network-acl-entry \
--network-acl-id acl-db123456 \
--rule-number 200 \
--protocol -1 \
--rule-action deny \
--ingress \
--cidr-block 0.0.0.0/0
Enable Monitoring & Intrusion Detection
~13 minutes
Comprehensive monitoring enables rapid detection of security incidents. GuardDuty uses machine learning to detect malicious activity, while Amazon Inspector identifies vulnerabilities. Least-privilege IAM roles limit the blast radius of compromises.
Enable GuardDuty
4.1 Enable GuardDuty
- Navigate to
GuardDutyservice in AWS Console - Click
Get startedandEnable GuardDuty - GuardDuty begins analyzing VPC Flow Logs, DNS logs, and CloudTrail events
- Initial setup takes 15-30 minutes to establish baseline behavior
# Enable GuardDuty
aws guardduty create-detector \
--enable \
--finding-publishing-frequency FIFTEEN_MINUTES
# Create SNS topic for alerts
aws sns create-topic --name guardduty-alerts
# Subscribe email to topic
aws sns subscribe \
--topic-arn arn:aws:sns:REGION:ACCOUNT:guardduty-alerts \
--protocol email \
--notification-endpoint EMAIL ADDRESS
# Create EventBridge rule for high-severity findings
aws events put-rule \
--name guardduty-high-severity \
--event-pattern '{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"],
"detail": {"severity": [7, 8, 9]}
}'
Enable Amazon Inspector
4.2 Enable Amazon Inspector
- Navigate to
Amazon Inspectorservice - Click
Get startedand enable Inspector - Choose EC2 instances and ECR repositories for scanning
- Inspector automatically scans when new instances launch or packages update
# Enable Inspector for EC2 and ECR
aws inspector2 enable --resource-types EC2 ECR
# List high and critical findings
aws inspector2 list-findings \
--filter-criteria '{
"severity": [
{"comparison": "EQUALS", "value": "HIGH"},
{"comparison": "EQUALS", "value": "CRITICAL"}
]
}'
Configure CloudWatch Monitoring
4.3 Enable Detailed Monitoring
- Select EC2 instances in console
Actions β Monitor and troubleshoot β Manage detailed monitoring- Enable detailed monitoring for 1-minute metrics
- Set up alarms for CPU, network, and disk anomalies
# Enable detailed monitoring
aws ec2 monitor-instances --instance-ids i-1234567890abcdef0
# Create alarm for high CPU (potential cryptomining)
aws cloudwatch put-metric-alarm \
--alarm-name "EC2-High-CPU-Alert" \
--alarm-description "Alert when CPU exceeds 80%" \
--metric-name CPUUtilization \
--namespace AWS/EC2 \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--alarm-actions arn:aws:sns:REGION:ACCOUNT:security-alerts \
--dimensions Name=InstanceId,Value=i-1234567890abcdef0
# Create alarm for high network out (potential data exfiltration)
aws cloudwatch put-metric-alarm \
--alarm-name "EC2-High-Network-Out" \
--alarm-description "Alert on high outbound traffic" \
--metric-name NetworkOut \
--namespace AWS/EC2 \
--statistic Average \
--period 300 \
--threshold 1000000000 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:REGION:ACCOUNT:security-alerts
Implement Least-Privilege IAM Roles
4.4 Create Application-Specific IAM Roles
Create separate roles for different application functions with minimum permissions:
# Create role for web application
aws iam create-role \
--role-name WebApp-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Create custom policy with specific S3 bucket access only
aws iam create-policy \
--policy-name WebApp-S3-Access \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::your-app-bucket/*"
}]
}'
# Attach policy to role
aws iam attach-role-policy \
--role-name WebApp-Role \
--policy-arn arn:aws:iam::ACCOUNT:policy/WebApp-S3-Access
Validate Your Configuration
Complete these checks to ensure your EC2 security hardening is properly implemented:
Validation Script
#!/bin/bash
# EC2 Security Configuration Validation Script
echo "=== EC2 Security Validation ==="
echo ""
# Check security groups for open SSH
echo "Checking for permissive security groups..."
OPEN_SSH=$(aws ec2 describe-security-groups \
--query 'SecurityGroups[?IpPermissions[?FromPort==`22` && IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]' \
--output text)
if [ -n "$OPEN_SSH" ]; then
echo "β WARNING: Security groups allow SSH from anywhere:"
echo "$OPEN_SSH"
else
echo "β No security groups allow SSH from anywhere"
fi
echo ""
# Check IMDSv2 enforcement
echo "Checking IMDSv2 enforcement..."
IMDSV1_INSTANCES=$(aws ec2 describe-instances \
--query 'Reservations[*].Instances[?MetadataOptions.HttpTokens==`optional`].[InstanceId]' \
--output text)
if [ -n "$IMDSV1_INSTANCES" ]; then
echo "β WARNING: Instances still allow IMDSv1:"
echo "$IMDSV1_INSTANCES"
else
echo "β All instances enforce IMDSv2"
fi
echo ""
# Check EBS encryption
echo "Checking EBS encryption..."
UNENCRYPTED=$(aws ec2 describe-volumes \
--query 'Volumes[?Encrypted==`false`].[VolumeId]' \
--output text)
if [ -n "$UNENCRYPTED" ]; then
echo "β WARNING: Unencrypted EBS volumes found:"
echo "$UNENCRYPTED"
else
echo "β All EBS volumes are encrypted"
fi
echo ""
# Check SSM agent connectivity
echo "Checking SSM agent connectivity..."
SSM_OFFLINE=$(aws ssm describe-instance-information \
--query 'InstanceInformationList[?PingStatus!=`Online`].[InstanceId,PingStatus]' \
--output text)
if [ -n "$SSM_OFFLINE" ]; then
echo "β Instances with SSM connectivity issues:"
echo "$SSM_OFFLINE"
else
echo "β All registered instances have healthy SSM connectivity"
fi
echo ""
# Check GuardDuty
echo "Checking GuardDuty..."
DETECTORS=$(aws guardduty list-detectors --query 'DetectorIds' --output text)
if [ -n "$DETECTORS" ]; then
echo "β GuardDuty is enabled"
else
echo "β GuardDuty is not enabled"
fi
echo ""
echo "EC2 security validation complete!"
Common Mistakes to Avoid
Allowing SSH/RDP from 0.0.0.0/0. Use bastion hosts, VPN, or Systems Manager Session Manager instead of exposing management ports to the internet.
Using IMDSv1 (optional tokens). Always require IMDSv2 tokens to prevent SSRF attacks from stealing IAM credentials.
Attaching AdministratorAccess to EC2 roles. Use least-privilege policies specific to each application's actual needs.
Storing credentials in user data or config files. Use IAM roles, Secrets Manager, or Parameter Store instead of hardcoded credentials.
Skipping patch testing. Always test patches in staging before production to avoid breaking applications.
Placing all instances in public subnets. Only load balancers and NAT gateways should have public IPs; keep application and database servers in private subnets.
Stop Managing EC2 Security Manually
With dozens of instances and constantly changing configurations, manual security management becomes impossible. AWSight automatically monitors your EC2 security posture 24/7, detects misconfigurations, and provides real-time insights across your entire fleet.