aws securityhub enable-security-hub --enable-default-standards enables Security Hub with CIS and FSBP standards; run prowler aws for a comprehensive open-source audit."Action": "*", "Resource": "*") -- the #1 root cause of privilege escalation in AWS. 83% of organizations experienced at least one cloud security incident in 2025, with 23% caused by misconfigurations.| # | AWS Service | Security Control | Risk if Missing | Config/Command |
|---|---|---|---|---|
| 1 | IAM | Enable MFA on root account | Full account takeover | Console > IAM > Root > Assign MFA |
| 2 | IAM | Use IAM roles, not long-lived access keys | Credential leaks, lateral movement | aws iam create-role --assume-role-policy-document |
| 3 | IAM | Enforce least-privilege policies (no wildcards) | Privilege escalation | IAM Access Analyzer + aws accessanalyzer |
| 4 | IAM | Enable IAM Access Analyzer | Undetected public/cross-account access | aws accessanalyzer create-analyzer --type ACCOUNT |
| 5 | S3 | Block Public Access at account level | Data breaches (most common misconfiguration) | aws s3control put-public-access-block --account-id |
| 6 | S3 | Enable default SSE-KMS encryption | Data exposure at rest | aws s3api put-bucket-encryption |
| 7 | S3 | Disable ACLs (use bucket policies only) | Complex, error-prone permissions | BucketOwnerEnforced ownership setting |
| 8 | EC2 | Encrypt all EBS volumes by default | Data exposure if disk stolen/shared | aws ec2 enable-ebs-encryption-by-default |
| 9 | EC2 | Use private subnets for backends | Direct internet exposure | VPC with public/private subnet architecture |
| 10 | RDS | Enable encryption at rest and in transit | Database data exposure | --storage-encrypted --kms-key-id |
| 11 | RDS | Deploy in private subnets, no public access | Database internet exposure | --no-publicly-accessible |
| 12 | Lambda | Use IAM roles with minimal permissions | Over-privileged function compromise | Per-function execution role |
| 13 | Lambda | Store secrets in Secrets Manager, not env vars | Credential exposure in console/logs | aws secretsmanager get-secret-value |
| 14 | VPC | Enable VPC Flow Logs | No network visibility for forensics | aws ec2 create-flow-logs |
| 15 | VPC | Restrict security groups (no 0.0.0.0/0 ingress) | Unauthorized access from internet | aws ec2 describe-security-groups audit |
| 16 | CloudTrail | Enable multi-region trail with log validation | No audit trail for incident response | aws cloudtrail create-trail --is-multi-region |
| 17 | GuardDuty | Enable in all regions | No threat detection | aws guardduty create-detector --enable |
| 18 | Security Hub | Enable with CIS + FSBP standards | No centralized security posture view | aws securityhub enable-security-hub |
| 19 | KMS | Use CMKs with automatic rotation | Non-compliant encryption, manual key management | aws kms enable-key-rotation |
| 20 | Organizations | Use SCPs to block dangerous actions | No preventive guardrails | aws organizations create-policy --type SERVICE_CONTROL_POLICY |
START: What AWS security area needs attention?
├── Account-level security?
│ ├── YES → Lock root account with MFA, enable Organizations + SCPs, enable Security Hub
│ └── NO ↓
├── Identity & Access (IAM)?
│ ├── YES → Use IAM Identity Center (SSO), roles not keys, least-privilege, Access Analyzer
│ └── NO ↓
├── Data storage (S3/EBS/RDS)?
│ ├── YES → Block Public Access (S3), encrypt at rest (KMS), encrypt in transit (TLS 1.2+)
│ └── NO ↓
├── Compute (EC2/Lambda)?
│ ├── YES → Private subnets, security groups, per-function IAM roles, Secrets Manager
│ └── NO ↓
├── Network (VPC)?
│ ├── YES → VPC Flow Logs, restrict security groups, use VPC endpoints for AWS services
│ └── NO ↓
├── Monitoring & Detection?
│ ├── YES → CloudTrail (all regions), GuardDuty, Security Hub, Config Rules
│ └── NO ↓
├── Compliance audit?
│ ├── YES → Run Prowler, enable CIS Benchmark in Security Hub, AWS Config conformance packs
│ └── NO ↓
└── DEFAULT → Start with IAM + CloudTrail + GuardDuty + S3 Block Public Access (highest impact)
The root account has unrestricted access to all resources. Enable hardware MFA, delete root access keys, and never use root for daily operations. [src1]
# Check if root account has MFA enabled
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'
# Check for root access keys (should return empty)
aws iam list-access-keys --user-name root 2>/dev/null || echo "Use Console to check root keys"
Verify: aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled' → expected: 1
Enable IAM Access Analyzer to detect public and cross-account access, then audit all policies for wildcard permissions. [src2]
# Create IAM Access Analyzer
aws accessanalyzer create-analyzer \
--analyzer-name account-analyzer \
--type ACCOUNT
# List all findings (public/cross-account access)
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer
Verify: aws accessanalyzer list-findings → expected: no ACTIVE findings
S3 is the most commonly misconfigured AWS service. Block public access at the account level. [src5]
# Block public access for entire account
aws s3control put-public-access-block \
--account-id $(aws sts get-caller-identity --query Account --output text) \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Verify: All four settings return true in output.
Enable a multi-region trail with log file validation and KMS encryption for tamper-proof audit logging. [src4]
# Create KMS key for CloudTrail encryption
KEY_ID=$(aws kms create-key --description "CloudTrail encryption key" \
--query 'KeyMetadata.KeyId' --output text)
aws kms enable-key-rotation --key-id $KEY_ID
# Create multi-region trail
aws cloudtrail create-trail \
--name organization-trail \
--s3-bucket-name my-cloudtrail-logs \
--is-multi-region-trail \
--enable-log-file-validation \
--kms-key-id $KEY_ID
aws cloudtrail start-logging --name organization-trail
Verify: aws cloudtrail get-trail-status --name organization-trail --query 'IsLogging' → expected: true
GuardDuty provides continuous threat detection; Security Hub aggregates findings into a single dashboard. [src3] [src7]
# Enable GuardDuty
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES
# Enable Security Hub with default standards (CIS + FSBP)
aws securityhub enable-security-hub --enable-default-standards
Verify: aws securityhub get-enabled-standards → returns list of enabled standards
Enable default EBS encryption in every region to ensure all new volumes are automatically encrypted. [src1]
# Enable default EBS encryption in current region
aws ec2 enable-ebs-encryption-by-default
# Verify
aws ec2 get-ebs-encryption-by-default
Verify: aws ec2 get-ebs-encryption-by-default --query 'EbsEncryptionByDefault' → expected: true
Prowler executes 240+ security checks based on CIS, NIST, PCI-DSS, HIPAA, and AWS best practices. [src6]
# Install and run Prowler
pip install prowler
prowler aws --compliance cis_3.0_aws -M html
Verify: Review the generated report in output/ directory. Address all FAIL and HIGH severity findings first.
# Input: AWS account with Terraform configured
# Output: IAM role with minimal S3 read-only access and confused deputy protection
resource "aws_iam_role" "app_role" {
name = "app-read-only"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
Condition = {
StringEquals = { "aws:SourceAccount" = data.aws_caller_identity.current.account_id }
}
}]
})
}
resource "aws_iam_role_policy" "s3_read" {
name = "s3-read-specific-bucket"
role = aws_iam_role.app_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:ListBucket"]
Resource = [
"arn:aws:s3:::my-app-data",
"arn:aws:s3:::my-app-data/*"
]
}]
})
}
# Input: AWS account with Terraform configured
# Output: S3 bucket with KMS encryption, versioning, and public access block
resource "aws_s3_bucket" "secure_bucket" {
bucket = "my-secure-data-bucket"
}
resource "aws_s3_bucket_versioning" "versioning" {
bucket = aws_s3_bucket.secure_bucket.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" {
bucket = aws_s3_bucket.secure_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3_key.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "block" {
bucket = aws_s3_bucket.secure_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Input: CloudFormation stack deployment
# Output: Security group allowing only HTTPS from corporate network
Resources:
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "HTTPS only from corporate network"
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/8
Description: "HTTPS from corporate"
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS outbound for API calls"
# Input: AWS account with VPC and RDS configured
# Output: Lambda function in private subnet with Secrets Manager access
resource "aws_iam_role" "lambda_role" {
name = "lambda-db-reader"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "lambda_policy" {
name = "lambda-minimal-access"
role = aws_iam_role.lambda_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue"]
Resource = [aws_secretsmanager_secret.db_creds.arn]
},
{
Effect = "Allow"
Action = ["rds-db:connect"]
Resource = ["arn:aws:rds-db:*:*:dbuser:*/lambda_user"]
}
]
})
}
// BAD -- grants full admin access to everything
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}]
}
// GOOD -- only the permissions the application actually needs
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}]
}
// BAD -- makes bucket contents publicly readable
resource "aws_s3_bucket_acl" "public" {
bucket = aws_s3_bucket.data.id
acl = "public-read"
}
// GOOD -- blocks all public access at bucket level
resource "aws_s3_bucket_public_access_block" "block" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
// BAD -- allows SSH from any IP address
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.server.id
}
// GOOD -- SSH only from bastion host security group
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion.id
security_group_id = aws_security_group.server.id
}
# BAD -- credentials visible in Lambda console, CloudWatch logs, API responses
import os
DB_PASSWORD = os.environ['DB_PASSWORD'] # Stored as Lambda env var
# GOOD -- secrets never stored in code or environment variables
import boto3
import json
def get_db_credentials():
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='prod/db/credentials')
return json.loads(response['SecretString'])
git-secrets pre-commit hook, rotate keys immediately if exposed, switch to IAM roles. [src2]--is-multi-region-trail. [src4]publicly_accessible = true. Fix: Explicitly set --no-publicly-accessible and deploy in private subnets. [src2]GetSecretValue at runtime. [src3]aws kms enable-key-rotation. [src1]# Run comprehensive Prowler security assessment
prowler aws --compliance cis_3.0_aws -M json html
# Check IAM credential report (users, MFA, key age)
aws iam generate-credential-report && sleep 5
aws iam get-credential-report --query 'Content' --output text | base64 -d
# Check S3 Block Public Access at account level
aws s3control get-public-access-block \
--account-id $(aws sts get-caller-identity --query Account --output text)
# Check CloudTrail status
aws cloudtrail describe-trails \
--query 'trailList[*].{Name:Name,Multi:IsMultiRegionTrail,Log:LogFileValidationEnabled}'
# List GuardDuty detectors
aws guardduty list-detectors
# Find security groups with 0.0.0.0/0 ingress
aws ec2 describe-security-groups \
--filters Name=ip-permission.cidr,Values=0.0.0.0/0 \
--query 'SecurityGroups[*].{ID:GroupId,Name:GroupName}'
# Check for publicly accessible RDS instances
aws rds describe-db-instances \
--query 'DBInstances[?PubliclyAccessible==`true`].DBInstanceIdentifier'
# Check EBS default encryption
aws ec2 get-ebs-encryption-by-default
# Audit VPC Flow Logs
aws ec2 describe-flow-logs --query 'FlowLogs[*].{VPC:ResourceId,Status:FlowLogStatus}'
| Standard/Tool | Version | Status | Key Changes |
|---|---|---|---|
| CIS AWS Foundations Benchmark | v3.0.0 | Current | Added IAM Access Analyzer checks, expanded S3 controls |
| CIS AWS Foundations Benchmark | v2.0.0 | Supported | Restructured sections, added Organizations controls |
| AWS Security Hub FSBP | 2025 | Current | 250+ controls across 30+ services |
| Prowler | 4.x | Current | Multi-cloud (AWS/Azure/GCP), 240+ AWS checks |
| Prowler | 3.x | Maintained | AWS-only, CIS v2.0 baseline |
| AWS Well-Architected Security Pillar | 2025 | Current | Updated identity, detection, and response guidance |
| Terraform AWS Provider | 5.x | Current | S3 bucket resources split into sub-resources |
| Terraform AWS Provider | 4.x | Maintained | Unified S3 bucket resource |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Setting up a new AWS account or organization | Using Azure, GCP, or multi-cloud | Cloud-specific security checklists |
| Preparing for a security audit (SOC 2, ISO 27001) | Need application-layer security (OWASP Top 10) | XSS/SQLi/CSRF prevention guides |
| Remediating Security Hub or Prowler findings | Need container/Kubernetes security | EKS security best practices |
| Hardening an existing AWS environment | Need compliance-specific controls (HIPAA, PCI) | AWS compliance-specific guides |
| Onboarding developers to AWS security practices | Managing on-premises infrastructure | On-prem security hardening guides |