8 AWS Misconfigurations That Will Get You Hacked (And How to Fix Them)

The most dangerous misconfigurations we see in production AWS environments — ranked by real-world risk, with detection commands and compliance impact for each.

Who this is for: DevOps engineers, SREs, and security teams managing production AWS workloads. If you're running services on AWS and haven't audited your configuration in the last 90 days, start here. These 8 misconfigurations account for the majority of AWS-related breaches we see in the wild — and every single one is detectable and fixable.

What We'll Cover

  1. S3 Public Buckets — The Open Door
  2. Overly Permissive IAM Policies
  3. Security Groups: SSH/RDP Open to the Internet
  4. RDS Publicly Accessible
  5. RDS Without Encryption at Rest
  6. Missing or Misconfigured CloudTrail
  7. Secrets in Lambda Environment Variables
  8. KMS Keys Without Annual Rotation

1. S3 Public Buckets — The Open Door

Public S3 buckets are the single most common AWS misconfiguration. A single bucket with BlockPublicAcls: false and a relaxed bucket policy can expose customer data, backups, database exports, or API keys to the entire internet. The 2019 Capital One breach started exactly this way.

Even buckets created "just for internal use" often end up public when someone uploads a file with the wrong ACL, or a bucket policy is modified without checking the block public access settings.

● Critical
S3 bucket allows public read or write access
Affects: S3 Buckets — All industries
HIPAA §164.312(a)(1) PCI-DSS 3.2 Req 3.4 SOC 2 CC6.6 GDPR Art. 32

Detection — AWS CLI

# Check all buckets for public access block
aws s3api list-buckets --query 'Buckets[].Name' --output text | while read bucket; do
  echo "=== $bucket ==="
  aws s3api get-public-access-block --bucket "$bucket" 2>/dev/null || echo "No block policy"
done

# Check bucket ACLs for public grants
aws s3api list-buckets --query 'Buckets[].Name' --output text | while read bucket; do
  acl=$(aws s3api get-bucket-acl --bucket "$bucket" 2>/dev/null)
  if echo "$acl" | grep -q "Grants.*URI.*http://acs.amazonaws.com/groups/global/AllUsers"; then
    echo "PUBLIC READ: $bucket"
  fi
done

How to fix it

  1. Enable S3 Block Public Access at the account level — this prevents any bucket from going public, even by accident
  2. Set BlockPublicAcls: true, IgnorePublicAcls: true, BlockPublicPolicy: true, RestrictPublicBuckets: true
  3. Audit existing buckets with aws s3api list-buckets --query 'Buckets[*].[Name]' and check each one
  4. Enable S3 server access logging to track all access requests
  5. Enable S3 versioning so you can recover from accidental overwrites

2. Overly Permissive IAM Policies

Action: "*" and Resource: "*" in an IAM policy is the cloud security equivalent of leaving your root password on a sticky note. These wildcard policies are the fastest path to privilege escalation — if an attacker gets even minimal access, they can escalate to full account control.

We also see iam:* attached to non-admin roles, inline policies with Effect: Allow on all actions, and permissions boundaries set too wide. Any of these creates a privilege escalation path.

● Critical
IAM policy allows full administrative access (Action: "*" or iam:*)
Affects: IAM Policies, Roles — All industries
PCI-DSS 4.0 Req 6.3.3 HIPAA §164.312(c)(1) SOC 2 CC6.1 NIST SP 800-53 AC-6

Detection — AWS CLI

# Find all policies with wildcard actions or resources
aws iam list-policies --scope Local --query 'Policies[*].[PolicyName,Arn]' --output text | while read name arn; do
  version=$(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text)
  doc=$(aws iam get-policy-version --policy-arn "$arn" --version-id "$version" --query 'PolicyVersion.Document' --output json)
  if echo "$doc" | grep -q '"Action":\n.*"[*]'; then
    echo "WILDCARD ACTION: $name ($arn)"
  fi
  if echo "$doc" | grep -q '"Resource":\n.*"[*]'; then
    echo "WILDCARD RESOURCE: $name ($arn)"
  fi
done

How to fix it

  1. Use AWS Access Analyzer to identify external access and overprivileged policies
  2. Apply least-privilege principle: each role gets only the permissions it needs for its specific task
  3. Enable IAM Access Advisors to see which services each role actually uses — remove everything else
  4. Set a permissions boundary on all roles to limit the maximum scope of permissions
  5. Replace inline policies with customer-managed policies so changes are tracked in version history

3. Security Groups: SSH/RDP Open to the Internet

A security group rule with 0.0.0.0/0 on port 22 (SSH) or port 3389 (RDP) is an open invitation for brute force attacks. Even if you think "it's just for admin access," exposed admin ports scan continuously across AWS IP ranges — you will be hit within hours of deployment.

We also see overly broad ranges like 10.0.0.0/8 when the actual requirement is a single jump host, and security groups that allow all traffic out (0.0.0.0/0 on all egress) which bypasses network-level controls.

● High
SSH (22) or RDP (3389) accessible from 0.0.0.0/0
Affects: EC2 Instances, RDS Proxies — All industries
CIS AWS Benchmark 4.1 PCI-DSS 4.0 Req 1.3 SOC 2 CC6.2

Detection — AWS CLI

# Find security groups with SSH/RDP open to 0.0.0.0/0
aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupId,GroupName]' --output text | while read sgid sgname; do
  rules=$(aws ec2 describe-security-groups --group-ids "$sgid" --query 'SecurityGroups[0].IpPermissions[]' --output json)
  if echo "$rules" | grep -q '"FromPort": 22'; then
    if echo "$rules" | grep -q '"CidrIp": "0.0.0.0/0"'; then
      echo "SSH OPEN TO INTERNET: $sgid ($sgname)"
    fi
  fi
  if echo "$rules" | grep -q '"FromPort": 3389'; then
    if echo "$rules" | grep -q '"CidrIp": "0.0.0.0/0"'; then
      echo "RDP OPEN TO INTERNET: $sgid ($sgname)"
    fi
  fi
done

How to fix it

  1. Use Systems Manager Session Manager or AWS SSM Fleet Manager instead of direct SSH — no port 22 needed, fully audited
  2. Use a bastion host in a dedicated admin subnet, not the default VPC
  3. If you must use SSH, lock it to known IP ranges via a Network ACL or a dedicated security group attached only to jump hosts
  4. For RDP, always use AWS Client VPN or AWS Site-to-Site VPN — never expose 3389 directly
  5. Enable VPC Flow Logs so you can audit what actually hit your security groups

4. RDS Publicly Accessible

Setting PubliclyAccessible: true on an RDS instance exposes your database to the internet. Even if the security group restricts access, this setting fundamentally defeats the network isolation that VPC provides — a misconfigured security group rule is all it takes for exposure.

● Critical
RDS instance has PubliclyAccessible set to true
Affects: RDS Instances (MySQL, PostgreSQL, Aurora, MariaDB) — All industries
HIPAA §164.312(a)(1) PCI-DSS 4.0 Req 3.2 SOC 2 CC6.7 GDPR Art. 32

Detection — AWS CLI

aws rds describe-db-instances --query 'DBInstances[*].[DBInstanceIdentifier,PubliclyAccessible]' --output text | grep -E 'true$'

How to fix it

  1. Set PubliclyAccessible: false on all RDS instances — this is a one-time fix via the ModifyDBInstance API (no reboot required)
  2. Connect via bastion host or VPN within the VPC
  3. For application servers in private subnets, use RDS in private subnets — no internet path exists
  4. For multi-region disaster recovery, use RDS read replicas in a secondary region with VPN connectivity, not public DNS

5. RDS Without Encryption at Rest

RDS instances created without StorageEncrypted: true store your data in plaintext on disk. AWS-managed encryption at rest uses AES-256 with AWS KMS keys — zero performance penalty, full compliance coverage. Unencrypted RDS is a direct violation of HIPAA and PCI-DSS storage requirements.

● High
RDS instance missing encryption at rest
Affects: RDS Instances (all engines) — Healthcare, Finance, any regulated industry
HIPAA §164.312(e)(1) PCI-DSS 4.0 Req 3.4 SOC 2 CC6.7 SOX CC7.1

Detection — AWS CLI

# Check all RDS instances for encryption status
aws rds describe-db-instances --query 'DBInstances[*].[DBInstanceIdentifier,StorageEncrypted,KmsKeyId]' --output text

How to fix it

You cannot enable encryption on an existing unencrypted RDS instance — you must create a new encrypted instance and migrate. This is why encryption should be mandatory at creation time. Use AWS Backup or DMS (Database Migration Service) to migrate with minimal downtime.

  1. Create a new encrypted RDS instance from a snapshot of the unencrypted database
  2. Update application connection strings to point to the new instance
  3. Validate data integrity before cutting over
  4. Delete the old unencrypted instance after migration is verified
  5. Use AWS Config rules to automatically alert on new unencrypted RDS creations

6. Missing or Misconfigured CloudTrail

CloudTrail is your audit log for everything that happens in your AWS account. Without it, you have no visibility into who did what, when, and from where. Compromises that should take minutes to detect become months-long breaches. The Uber breach of 2016 is a textbook case of what happens when audit logs aren't reviewed.

● High
CloudTrail not enabled, not multi-region, or S3 log bucket not protected
Affects: Entire AWS account — All industries
HIPAA §164.312(b) PCI-DSS 4.0 Req 10.1 SOC 2 CC7.2 SOX CC8.1 NIST SP 800-53 AU-2

Detection — AWS CLI

# Check if CloudTrail is enabled and configured
aws cloudtrail describe-trails --query 'Trails[*].[Name,IsMultiAccountTrail,IsOrganization,IsLogging]' --output text

# Verify log file validation is enabled (SHA-256 integrity)
aws cloudtrail get-trail-status --name YOUR_TRAIL_NAME --query 'LogFileValidationEnabled'

How to fix it

  1. Create a multi-region CloudTrail that writes to a dedicated S3 bucket — never the same bucket as your application data
  2. Enable log file validation (LogFileValidationEnabled: true) so tampering with logs is detectable
  3. Enable CloudTrail Insights to detect anomalies automatically (e.g., spike in Describe calls, unusual API activity)
  4. Store CloudTrail logs in a separate account (Security Tooling account) so a compromised application account can't alter its own logs
  5. Enable S3 object lock on the log bucket to prevent deletion of historical logs

7. Secrets in Lambda Environment Variables

Storing API keys, database passwords, or Stripe tokens in Lambda environment variables is one of the most common cloud security assessment findings. Environment variables are visible in plain text in the AWS console, logged in CloudWatch, and stored in function configuration files. Anyone with lambda:GetFunctionConfiguration permission can read them.

The most common culprits: STRIPE_SECRET_KEY, AWS_SECRET_ACCESS_KEY, DATABASE_PASSWORD, API_KEY, and GITHUB_TOKEN. If any of these are in your Lambda config, you have a critical exposure.

● Critical
Lambda function environment variables contain plaintext secrets
Affects: Lambda Functions — All industries using Lambda
PCI-DSS 4.0 Req 3.4 HIPAA §164.312(d) SOC 2 CC6.7

Detection — AWS CLI

# Check all Lambda functions for plaintext secrets in env vars
aws lambda list-functions --query 'Functions[*].FunctionName' --output text | while read fn; do
  env=$(aws lambda get-function-configuration --function-name "$fn" --query 'Environment.Variables' --output json)
  if echo "$env" | grep -iE 'PASSWORD|SECRET|KEY|TOKEN|PRIVATE'; then
    echo "SECRETS IN ENV: $fn"
  fi
done

How to fix it

  1. Move all secrets to AWS Secrets Manager or AWS Systems Manager Parameter Store (use the SecureString type)
  2. Grant Lambda functions minimal IAM permissions to secretsmanager:GetSecretValue for the specific secret they need
  3. Enable Lambda environment variable encryption at rest (AWS KMS) — this is free and enabled by default on new functions
  4. Use Lambda Layers to inject secrets at deployment time rather than storing them in config
  5. Never log process.env or environment objects in Lambda — CloudWatch logs are often accessible to DevOps engineers who shouldn't see production credentials

8. KMS Keys Without Annual Rotation

AWS KMS keys that don't have automatic annual rotation enabled accumulate risk over time. The longer a key is in use, the larger the blast radius if it's ever compromised. PCI-DSS 4.0 explicitly requires annual rotation for customer-managed keys used in cardholder data environments.

Equally dangerous: KMS keys pending deletion. AWS enforces a minimum 7-day waiting period before deletion — if you accidentally scheduled deletion on a key protecting production data, you have 7 days to cancel before that data is permanently inaccessible.

● Medium
KMS customer-managed key missing automatic annual rotation
Affects: KMS Keys — Finance, Healthcare, any regulated industry
PCI-DSS 4.0 Req 3.6 HIPAA §164.312(e)(1) SOC 2 CC6.7 NIST SP 800-57

Detection — AWS CLI

# List CMK keys and check rotation status
aws kms list-keys --query 'Keys[*].[KeyId,KeyArn]' --output text | while read kid arn; do
  rotation=$(aws kms get-key-rotation-status --key-id "$kid" --query 'KeyRotationEnabled' --output text)
  if [ "$rotation" = "False" ]; then
    alias=$(aws kms list-aliases --key-id "$kid" --query 'Aliases[0].AliasName' --output text 2>/dev/null || echo "no-alias")
    echo "ROTATION DISABLED: $kid (alias: $alias)"
  fi
done

# Check for keys pending deletion
aws kms schedule-key-deletion --key-id YOUR_KEY_ID --pending-window-in-days 7 --dry-run 2>&1 | head -5

How to fix it

  1. Enable automatic key rotation for all customer-managed KMS keys: aws kms enable-key-rotation --key-id <key-id>
  2. Rotation is free and uses the same key ID — no migration needed, existing data stays accessible
  3. Set a calendar reminder 30 days before any key deletion window expires
  4. Use AWS Config rules to detect keys with rotation disabled and auto-remediate

Find These Issues in Your AWS Environment

Guardrail scans all 8 of these misconfigurations — plus 24 more — and maps each finding to HIPAA, PCI-DSS 4.0, and SOX compliance requirements. No AWS credentials required for the assessment.

Run a Free AWS Security Assessment →