For the complete documentation index, see llms.txt. This page is also available as Markdown.

PassRole & Service Principals

AWS IAM: PassRole & Service Principals

Theory

The iam:PassRole permission is a critical and often misunderstood control in AWS. Unlike standard IAM permissions that let you perform actions directly, PassRole allows you to delegate permissions to an AWS service. It enables you to attach an IAM role to a service (e.g., EC2, Lambda), which then assumes that role and inherits all its permissions, making it a potential privilege escalation vector.

When launching services like EC2, Lambda, or ECS, you can specify a role (instance profile or execution role) that grants access to other AWS resources. For example, an EC2 instance with an S3-access role allows applications on that instance to interact with S3. To do this, you need both:

  • Permission to create the service (e.g., ec2:RunInstances, lambda:CreateFunction)

  • Permission to pass the role (iam:PassRole)

PassRole exists to prevent privilege escalation. Without it, a user with service creation permissions could attach a highly privileged role (e.g., admin), gain access via the service, and take over the account. However, if PassRole itself is too broadly granted, it becomes the escalation path.

Scope matters:

  • Dangerous: iam:PassRole with Resource: "*" → can pass any role to any service

  • Safer: Restrict to specific role ARNs

  • Safer +: Add conditions like iam:PassedToService to limit which services can receive the role (e.g., only Lambda)

In short, iam:PassRole is a guardrail but misconfigured, it becomes the vulnerability.

Service Principals

Service principals are identifiers that represent AWS services in IAM trust policies. When an IAM role is created, its trust policy defines which principals can assume it. For service roles, this means specifying an AWS service (e.g., lambda.amazonaws.com, ec2.amazonaws.com) instead of a user or another role.

A typical service trust policy looks like this:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Service": "lambda.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
  }]
}

This allows the Lambda service to assume the role. When a Lambda function is created with this role, AWS automatically assumes it and uses its permissions during execution. The function can then perform any գործող actions allowed by the role (e.g., accessing DynamoDB or writing logs).

Each AWS service has its own service principal. Common examples include:

  • ec2.amazonaws.com → EC2 instances

  • lambda.amazonaws.com → Lambda functions

  • ecs-tasks.amazonaws.com → ECS tasks

  • cloudformation.amazonaws.com → CloudFormation

  • glue.amazonaws.com → Glue jobs

  • codebuild.amazonaws.com → CodeBuild

From an offensive perspective, service principals define where a role can be used. If a role trusts lambda.amazonaws.com, it can be passed to Lambda; if it trusts ec2.amazonaws.com, it can be attached to EC2. By enumerating roles and their trust policies, you can map which services are viable for exploitation when you have iam:PassRole, and identify privilege escalation paths accordingly.

Instance Metadata Service and Service Credentials

When an AWS service assumes a role, it receives temporary credentials similar to those issued via sts:AssumeRole. The key difference is how those credentials are exposed—each service provides them through its own mechanism rather than returning them directly via an API.

For EC2, credentials are delivered through the Instance Metadata Service (IMDS) at 169.254.169.254. When an instance is launched with a role, AWS automatically generates temporary credentials and exposes them at:

These include:

  • AccessKeyId (starts with ASIA)

  • SecretAccessKey

  • SessionToken

  • Expiration

They are automatically rotated, ensuring continuous access while the instance is running.

For Lambda, credentials are injected directly into the runtime via environment variables:

  • AWS_ACCESS_KEY_ID

  • AWS_SECRET_ACCESS_KEY

  • AWS_SESSION_TOKEN

The AWS SDK automatically uses these, so any API call made from the function implicitly uses the function’s execution role.

For ECS tasks, credentials are exposed via a task-specific metadata endpoint. The URL is provided in:

Querying this endpoint returns the same type of temporary credentials, usable directly with the AWS SDK. Understanding these delivery paths is key to leveraging iam:PassRole: once a role is attached to a service, retrieving its credentials allows you to operate with that role’s permissions.

Practice

Reconnaissance

The first step in exploiting PassRole is identifying which roles exist in the account and which services they trust. Roles with service principal trust policies are your targets for PassRole privilege escalation.

Enumerating roles and their trust policies requires listing all roles and retrieving each role's trust policy document to analyze which services can assume it.

Look for roles with service principal trust policies for services you can interact with, especially Lambda, EC2, ECS, CloudFormation, Glue, and Data Pipeline.

For each promising role, check what permissions it has by listing its attached policies and inline policies to determine if escalating to that role would be valuable.

Identifying PassRole Permissions

Once you've identified target roles with service trust policies, you need to determine whether you have the iam:PassRole permission required to pass those roles to services.

Checking for PassRole permissions requires enumerating all policies attached to your current identity and searching for PassRole statements.

PassRole permissions can be granted in several ways:

  • iam:PassRole with Resource: "*" allows passing any role,

  • iam:PassRole with specific role ARNs limits which roles you can pass,

  • and conditions using iam:PassedToService restrict which services can receive roles.

Understanding the scope of your PassRole permission is crucial for identifying valid escalation paths.

The below sections will cover common PassRole attack vectors for different instances. However, for detailed service-specific exploitation techniques, see the dedicated service pages.

PassRole to Lambda - Code Execution

Lambda functions provide the most direct and powerful privilege escalation path when combined with PassRole. You can create a Lambda function with arbitrary code, pass a privileged role to the function, and then invoke the function to execute your code with the role's permissions.

The privilege escalation involves creating a Lambda function, passing a privileged role as the execution role, and then invoking the function with code that leverages the role's permissions.

This basic example lists IAM users, but you can modify the function code to perform any action allowed by the role's permissions. For maximum flexibility, create a function that accepts commands through the event parameter and executes them dynamically. You could exfiltrate data from S3, create access keys for users, modify security groups, or perform any other privileged operation.

An other example below, where we create a new access key for an administrator user and returns the credentials

PassRole to EC2 - Instance Metadata Access

EC2 instances provide a slightly slower but still effective privilege escalation path. You launch an instance with a privileged role attached via an instance profile, then access the instance to retrieve the role's temporary credentials from the Instance Metadata Service.

The escalation involves creating an instance profile, adding the privileged role to the profile, launching an EC2 instance with the profile, and then accessing the instance to retrieve credentials.

This method is more complex than Lambda because it requires waiting for the instance to launch, potentially configuring networking and security groups for SSH access, and manually retrieving credentials. However, it's useful when Lambda is not available or when you want to maintain persistent access to the credentials (the instance keeps receiving refreshed credentials from IMDS as long as it runs).

For faster access without SSH, use user data scripts to exfiltrate the credentials automatically when the instance boots. The user data script can retrieve credentials from IMDS and send them to an S3 bucket, an external server, or execute actions directly using the credentials.

PassRole to CloudFormation - Stack Execution

CloudFormation provides a unique privilege escalation vector because CloudFormation stacks can create, modify, and delete arbitrary AWS resources. By passing a privileged role to a CloudFormation stack, you can create resources that wouldn't normally be allowed by your permissions.

CloudFormation stacks execute with the permissions of the passed role, not your own permissions. This means even if you don't have iam:CreateUser or iam:AttachUserPolicy, the CloudFormation stack can create users and attach policies because the role you passed has those permissions.

The stack essentially acts as a privileged automation tool that executes whatever resource definitions you provide in the template.

PassRole to Glue - Job Execution

AWS Glue jobs can execute arbitrary Python or Scala code with permissions from a passed role. While less commonly known than Lambda or EC2, Glue provides another code execution path for privilege escalation.

Glue jobs run in a managed environment and can access AWS services using the role you pass. The output appears in CloudWatch Logs, so you can exfiltrate data or credentials through log messages, or have the script directly perform actions like creating access keys or modifying resources.

PassRole to Data Pipeline - Custom Activities

AWS Data Pipeline can execute arbitrary shell commands through custom activities, providing another code execution path when you can pass roles to the datapipeline service.

Data Pipeline is less commonly used than Lambda or EC2, which may make it less monitored for suspicious activity. However, it requires more setup and S3 buckets for storing outputs, making it slightly more complex than direct Lambda execution.

PassRole to ECS - Container Execution

ECS tasks can run containers with arbitrary code, using permissions from a passed task execution role. This provides a containerized environment for privilege escalation.

ECS tasks provide a containerized environment where you can run any Docker image with commands that leverage the passed role's permissions. The task role grants permissions to the container, while the execution role grants permissions to the ECS service itself to pull images and write logs.

Resources

Last updated