> AWS Misconfigurations Attackers Actually Exploit_
Cloud breaches look different from traditional ones. There's no lateral movement through workstations — instead, an exposed key in a GitHub commit leads to full account takeover in minutes. An EC2 instance with default metadata access lets a web server become a credential theft vector. An S3 bucket with a world-readable ACL leaks your entire customer database.
These aren't theoretical. They're the actual patterns behind real breaches — Capital One, Toyota, LastPass. This post covers the most common, how to find them, and how to fix them.
## 1. S3 bucket misconfiguration
The classic. An S3 bucket with public read access exposes everything inside.
Finding public buckets:
# Try accessing the bucket directly
curl https://target-bucket.s3.amazonaws.com/
# List contents if ListBucket is allowed
aws s3 ls s3://target-bucket/ --no-sign-request
# Find buckets via certificate transparency, GitHub, JS files
# Tools:
# - bucket_finder (bruteforce bucket names)
# - lazys3 (common name permutations)
# - S3Scanner
# Check your own buckets for public access
aws s3api get-bucket-acl --bucket my-bucket
aws s3api get-bucket-policy --bucket my-bucket
# List all buckets and check each
aws s3 ls
for bucket in $(aws s3 ls | awk '{print $3}'); do
echo "=== $bucket ==="
aws s3api get-bucket-public-access-block --bucket "$bucket" 2>/dev/null || echo "No public access block"
doneWhat attackers look for inside buckets:
- >
.envfiles, config files with credentials - >Database backups (
.sql,.dump,.bak) - >CloudFormation templates (often contain hardcoded values)
- >Private keys, certificates
- >PII (customer data, exports)
Fix:
# Enable S3 Block Public Access at the account level
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"## 2. IMDSv1 and SSRF → credential theft
EC2 instances have a metadata service at http://169.254.169.254. It provides temporary IAM credentials for the instance role. By default (IMDSv1), any process on the instance — or any SSRF vulnerability in an app running on it — can read these credentials.
From an SSRF vulnerability:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
→ Returns role name, e.g.: "ec2-prod-role"
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-prod-role
→ Returns:
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "...",
"Expiration": "2025-11-25T12:00:00Z"
}
These credentials have whatever permissions the instance's IAM role has. If the role has AdministratorAccess — you own the account.
Use the stolen credentials:
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
aws sts get-caller-identity # confirm who you are
aws s3 ls # list all buckets
aws iam list-roles # enumerate IAM rolesFix: Enforce IMDSv2, which requires a token exchange that HTTP redirect-based SSRF can't complete:
# Require IMDSv2 on an existing instance
aws ec2 modify-instance-metadata-options \
--instance-id i-0abcdef1234567890 \
--http-tokens required \
--http-put-response-hop-limit 1
# Set default to IMDSv2 for all new instances in the account
aws ec2 modify-instance-metadata-defaults \
--http-tokens required## 3. Over-permissive IAM policies
### The * wildcard problem
A common pattern in IAM policies:
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}That's AdministratorAccess. It appears in service accounts, Lambda execution roles, developer IAM users — often because it was "temporary" and never cleaned up.
Find over-permissive roles:
# List all policies attached to all roles
aws iam list-roles --query 'Roles[*].RoleName' --output text | tr '\t' '\n' | while read role; do
echo "=== $role ==="
aws iam list-attached-role-policies --role-name "$role" \
--query 'AttachedPolicies[*].PolicyName' --output text
done
# Find roles with AdministratorAccess
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# Use tools: Cloudsploit, Prowler, PMapper (finds privilege escalation paths)### IAM privilege escalation paths
PMapper maps trust relationships between IAM entities and finds if a low-privilege user can escalate to admin. Common paths:
- >
iam:CreatePolicyVersion— create a new version of any managed policy with*permissions - >
iam:AttachRolePolicy— attachAdministratorAccessto a role you can assume - >
iam:PassRole+ec2:RunInstances— launch an EC2 with an admin role, use IMDS to steal creds - >
lambda:CreateFunction+iam:PassRole+lambda:InvokeFunction— create a Lambda with admin role, invoke it to exfil credentials
# Run PMapper to find privilege escalation paths
pip install principalmapper
pmapper --profile default graph create
pmapper --profile default analysis --find-risks## 4. Exposed access keys in code
The most common entry point into an AWS account.
# Search GitHub for leaked keys
# (GitHub secret scanning catches most, but historical commits aren't always cleaned)
# Scan your own repos
git log -p | grep -E "(AKIA|ASIA)[A-Z0-9]{16}"
git log -p | grep -i "aws_secret"
trufflehog git file:///path/to/repo
gitleaks detect --source . --verbose
# Validate a key
aws sts get-caller-identity --profile leaked-key-profileWhat to do when you find a leaked key:
# Immediately deactivate
aws iam update-access-key --access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
# Check what it did (CloudTrail)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
--start-time 2025-01-01 \
--max-results 50
# Create a new key, rotate all systems using the old one, then delete
aws iam delete-access-key --access-key-id AKIAIOSFODNN7EXAMPLEPrevention: Use IAM roles wherever possible. For CI/CD, use OIDC federation (GitHub Actions → AssumeRoleWithWebIdentity) instead of long-term keys. Enable AWS Secrets Manager / Parameter Store for secrets at rest.
## 5. Unrestricted security groups
Security groups act as firewall rules. A common misconfiguration:
{
"IpProtocol": "-1",
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]
}That's all traffic, all ports, from anywhere. Often created for "quick debugging" and left open.
# Find security groups with 0.0.0.0/0 inbound
aws ec2 describe-security-groups \
--filters "Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query 'SecurityGroups[*].{ID:GroupId,Name:GroupName,Rules:IpPermissions}' \
--output json
# Find which instances use them
aws ec2 describe-instances \
--filters "Name=network-interface.group-id,Values=sg-xxxxxxxx" \
--query 'Reservations[*].Instances[*].{ID:InstanceId,IP:PublicIpAddress}'Particularly dangerous: port 22 (SSH), port 3389 (RDP), port 27017 (MongoDB), port 6379 (Redis) exposed to 0.0.0.0/0.
## 6. CloudTrail disabled
CloudTrail is AWS's audit log. Without it, attackers can operate with minimal evidence.
# Check if CloudTrail is enabled
aws cloudtrail get-trail-status --name your-trail-name
aws cloudtrail describe-trails --include-shadow-trails false
# Check for recent suspicious events
aws cloudtrail lookup-events \
--start-time $(date -d '24 hours ago' +%Y-%m-%dT%H:%M:%S) \
--query 'Events[?EventName==`CreateAccessKey` || EventName==`AttachUserPolicy` || EventName==`PutUserPolicy`]'Signs of account compromise in CloudTrail:
- >
ConsoleLoginfrom unusual geography - >
CreateAccessKeyfor existing users - >
AttachUserPolicy/AttachRolePolicywith admin policies - >Disabling GuardDuty or CloudTrail
- >
RunInstanceswith unknown AMIs or regions you don't use
## Automated scanning tools
| Tool | What it finds |
|---|---|
| Prowler | 250+ CIS benchmark checks across all AWS services |
| ScoutSuite | Multi-cloud security posture assessment |
| CloudSploit | Real-time misconfiguration scanning |
| Pacu | AWS exploitation framework (red team) |
| PMapper | IAM privilege escalation path analysis |
| TruffleHog / Gitleaks | Secret scanning in git history |
| S3Scanner | Public S3 bucket finder |
# Prowler quick scan (uses your default AWS profile)
pip install prowler
prowler aws --profile default -f eu-west-1## Priority hardening checklist
| Priority | Action |
|---|---|
| 1 | Enable S3 Block Public Access at the account level |
| 2 | Enforce IMDSv2 on all EC2 instances |
| 3 | Rotate or delete unused IAM access keys |
| 4 | Enable MFA on all IAM users and especially root |
| 5 | Enable CloudTrail in all regions, centralise to S3 |
| 6 | Enable GuardDuty |
| 7 | Review IAM roles for * wildcards, apply least privilege |
| 8 | Lock down security groups — no 0.0.0.0/0 on admin ports |
| 9 | Enable AWS Config rules for continuous compliance |
| 10 | Set up SCPs in AWS Organizations to prevent privilege misuse |
Cloud security isn't fundamentally different from on-prem security — it's the same principles (least privilege, logging, network segmentation) applied to APIs instead of servers. The difference is the blast radius: a single leaked key can expose your entire infrastructure in minutes.