HIGH

Cloud Security Misconfigurations: The Breaches That Didn't Have to Happen

An analysis of the most common cloud security misconfigurations across AWS, Azure, and GCP, with real breach examples and remediation steps.

The Misconfiguration Epidemic

Cloud misconfigurations are responsible for 65-70% of all cloud security incidents. Despite the shared responsibility model, organizations consistently make preventable mistakes that expose sensitive data.

The Shared Responsibility Model

┌─────────────────────────────────────────────────────────┐
│             Shared Responsibility Model                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Customer Responsibility (IN the cloud):                │
│  ┌─────────────────────────────────────────────────┐   │
│  │ • Data                                           │   │
│  │ • Identity & Access Management                   │   │
│  │ • Application Security                           │   │
│  │ • Network Configuration                          │   │
│  │ • Encryption Configuration                       │   │
│  │ • Operating System Patches                       │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  Cloud Provider Responsibility (OF the cloud):          │
│  ┌─────────────────────────────────────────────────┐   │
│  │ • Physical Security                              │   │
│  │ • Network Infrastructure                         │   │
│  │ • Hypervisor                                     │   │
│  │ • Storage Infrastructure                         │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

Top 10 Cloud Misconfigurations

1. Public Storage Buckets

The Classic Mistake: S3 buckets, Azure Blob containers, or GCS buckets configured for public access.

Real Breaches:

  • Capital One (2019): 100M+ customer records from misconfigured WAF + S3
  • Twitch (2021): 125GB source code from misconfigured server
  • Microsoft (2022): 65,000+ customer records from Azure Blob

AWS S3 - Finding Public Buckets:

# List bucket ACL
aws s3api get-bucket-acl --bucket my-bucket

# Check bucket policy
aws s3api get-bucket-policy --bucket my-bucket

# Check public access block
aws s3api get-public-access-block --bucket my-bucket

Remediation:

// S3 bucket policy - deny all public access
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPublicAccess",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}
# Enable S3 Block Public Access (account level)
aws s3control put-public-access-block \
  --account-id 123456789012 \
  --public-access-block-configuration \
  "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

2. Overly Permissive IAM Policies

The Mistake: Using wildcard permissions or overly broad access.

Dangerous Example:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}

Remediation - Least Privilege:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::specific-bucket/specific-prefix/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "10.0.0.0/8"
        }
      }
    }
  ]
}

AWS IAM Access Analyzer:

# Find unused permissions
aws accessanalyzer create-analyzer \
  --analyzer-name my-analyzer \
  --type ACCOUNT

# Generate policy based on access activity
aws accessanalyzer start-policy-generation \
  --policy-generation-details '{"principalArn":"arn:aws:iam::123456789012:role/MyRole"}'

3. Unencrypted Data at Rest

The Mistake: Storing sensitive data without encryption.

Check Encryption Status:

# AWS - Check S3 bucket encryption
aws s3api get-bucket-encryption --bucket my-bucket

# AWS - Check EBS volume encryption
aws ec2 describe-volumes --query 'Volumes[*].[VolumeId,Encrypted]'

# Azure - Check storage account encryption
az storage account show --name mystorageaccount --query 'encryption'

Enable Default Encryption:

# AWS - Enable default S3 encryption
aws s3api put-bucket-encryption --bucket my-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/my-key"
      },
      "BucketKeyEnabled": true
    }]
  }'

# Enable EBS encryption by default
aws ec2 enable-ebs-encryption-by-default

4. Exposed Secrets in Environment Variables

The Mistake: Hardcoding secrets or exposing them in logs/metadata.

Finding Exposed Secrets:

# Check Lambda environment variables
aws lambda get-function-configuration --function-name my-function \
  --query 'Environment.Variables'

# Check EC2 user data (often contains secrets)
aws ec2 describe-instance-attribute \
  --instance-id i-1234567890abcdef0 \
  --attribute userData \
  --query 'UserData.Value' --output text | base64 -d

Secure Secret Management:

# Using AWS Secrets Manager
import boto3

def get_secret():
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(SecretId='my-secret')
    return response['SecretString']

# Using Azure Key Vault
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://myvault.vault.azure.net", credential=credential)
secret = client.get_secret("my-secret")

5. Security Groups / NSGs Open to the World

The Mistake: 0.0.0.0/0 inbound rules on sensitive ports.

Dangerous Ports Often Exposed:

  • SSH (22)
  • RDP (3389)
  • Database ports (3306, 5432, 27017, 6379)
  • Admin interfaces (8080, 9200, 9090)

Finding Open Security Groups:

# AWS - Find security groups with 0.0.0.0/0 inbound
aws ec2 describe-security-groups \
  --query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].[GroupId,GroupName]'

# Azure - Find open NSG rules
az network nsg list --query '[].{Name:name, Rules:securityRules[?sourceAddressPrefix==`*`].{Port:destinationPortRange, Access:access}}'

Remediation:

# Remove dangerous inbound rule
aws ec2 revoke-security-group-ingress \
  --group-id sg-12345678 \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# Add restricted rule
aws ec2 authorize-security-group-ingress \
  --group-id sg-12345678 \
  --protocol tcp \
  --port 22 \
  --cidr 10.0.0.0/8

6. Disabled Logging and Monitoring

The Mistake: Not enabling CloudTrail, VPC Flow Logs, or audit logging.

Enable Comprehensive Logging:

# AWS - Enable CloudTrail (multi-region)
aws cloudtrail create-trail \
  --name my-trail \
  --s3-bucket-name my-audit-bucket \
  --is-multi-region-trail \
  --enable-log-file-validation

aws cloudtrail start-logging --name my-trail

# Enable VPC Flow Logs
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-12345678 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name vpc-flow-logs

# Enable S3 access logging
aws s3api put-bucket-logging --bucket my-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "my-logs-bucket",
      "TargetPrefix": "s3-access-logs/"
    }
  }'

7. Unrotated Access Keys

The Mistake: Long-lived credentials that are never rotated.

Find Old Access Keys:

# AWS - Find keys older than 90 days
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
  awk -F',' 'NR>1 && $9!="N/A" && (systime()-mktime(gensub(/[-:T]/," ","g",$10)" 0 0"))>7776000 {print $1,$9,$10}'

Automate Key Rotation:

import boto3
from datetime import datetime, timedelta

iam = boto3.client('iam')

def rotate_old_keys(max_age_days=90):
    users = iam.list_users()['Users']

    for user in users:
        keys = iam.list_access_keys(UserName=user['UserName'])['AccessKeyMetadata']

        for key in keys:
            age = (datetime.now(key['CreateDate'].tzinfo) - key['CreateDate']).days

            if age > max_age_days:
                print(f"Rotating key {key['AccessKeyId']} for {user['UserName']} (age: {age} days)")
                # Create new key, update application, delete old key

8. Missing MFA

The Mistake: Not requiring MFA for privileged access.

Enforce MFA:

// IAM policy requiring MFA for all actions
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllExceptListedIfNoMFA",
      "Effect": "Deny",
      "NotAction": [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:GetUser",
        "iam:ListMFADevices",
        "iam:ListUsers"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

9. Default VPC Usage

The Mistake: Running production workloads in default VPC with default settings.

Issues with Default VPC:

  • Public subnets by default
  • IGW attached automatically
  • Less segmentation control

Create Custom VPC:

# Create VPC with private subnets
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=production-vpc}]'

# Create private subnet (no route to IGW)
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 --availability-zone us-east-1a

# Delete default VPC (after migrating workloads)
aws ec2 delete-vpc --vpc-id vpc-default

10. Snapshot and Backup Exposure

The Mistake: EBS snapshots, RDS snapshots, or AMIs shared publicly.

Find Public Snapshots:

# Find public EBS snapshots
aws ec2 describe-snapshots --owner-ids self \
  --query 'Snapshots[?contains(to_string(CreateVolumePermissions), `all`)].[SnapshotId,Description]'

# Find public RDS snapshots
aws rds describe-db-snapshots --snapshot-type manual \
  --query 'DBSnapshots[?PubliclyAccessible==`true`].[DBSnapshotIdentifier]'

# Find public AMIs
aws ec2 describe-images --owners self \
  --query 'Images[?Public==`true`].[ImageId,Name]'

Make Private:

# Remove public access from snapshot
aws ec2 modify-snapshot-attribute \
  --snapshot-id snap-1234567890abcdef0 \
  --attribute createVolumePermission \
  --operation-type remove \
  --group-names all

Cloud Security Posture Management (CSPM)

Use automated tools to continuously scan for misconfigurations:

Open Source:

  • Prowler (AWS)
  • ScoutSuite (Multi-cloud)
  • CloudSploit (Multi-cloud)

Commercial:

  • Prisma Cloud
  • Wiz
  • Orca Security
  • Lacework

Running Prowler:

# Install
pip install prowler

# Run full assessment
prowler aws --output-formats html,json

# Specific checks
prowler aws --checks s3_bucket_public_access iam_policy_no_statements_with_admin_access

Compliance Frameworks

Compliance Mapping:
  CIS Benchmarks:
    - AWS Foundations Benchmark
    - Azure Foundations Benchmark
    - GCP Foundations Benchmark

  Regulatory:
    - SOC 2 Type II
    - PCI DSS
    - HIPAA
    - GDPR

  Industry:
    - NIST CSF
    - ISO 27001

References


The cloud doesn’t make you insecure—misconfiguration does. Know your responsibility.