This issue has been assessed as critical severity. Review affected configurations immediately.
AWS IAM privilege escalation is one of the most reliable paths from a low-privilege foothold to full account compromise. Two permissions stand out as especially dangerous: iam:PassRole and iam:CreatePolicyVersion. Neither shows up in routine permission audits the way iam:* does, yet each alone can hand an attacker administrative access within minutes.
Understanding iam:PassRole
iam:PassRole allows a principal to attach an IAM role to an AWS service — for example, assigning a role to an EC2 instance, a Lambda function, or a Glue job. The permission itself sounds innocuous: “you can pass a role to a service.” The danger is that the service receiving that role then acts with the role’s permissions, not the caller’s.
The Escalation Chain
Consider a developer with the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:PassRole",
"lambda:CreateFunction",
"lambda:InvokeFunction"
],
"Resource": "*"
}
]
}
This looks like a developer policy — Lambda deployment access. But with iam:PassRole on *, the developer can:
- Create a Lambda function and assign it the
AdministratorAccess-equivalent role - Write a Lambda handler that calls
sts:GetCallerIdentityandiam:CreateUserwith admin privileges - Invoke the function
The Lambda runs as the attached role, not as the developer. The developer has never been granted iam:CreateUser directly, but the end result is full privilege escalation.
High-Risk Service Pairings
iam:PassRole becomes critical when combined with any of these:
| Service Permission | Escalation Method |
|---|---|
lambda:CreateFunction + lambda:InvokeFunction | Run arbitrary code as a privileged role |
ec2:RunInstances | Launch an EC2 with an instance profile; access via SSM or metadata |
glue:CreateJob + glue:StartJobRun | Execute a Glue Python job as a privileged role |
ecs:RunTask | Run an ECS task with a task execution role |
sagemaker:CreateTrainingJob | Execute a SageMaker training job with an attached role |
codebuild:StartBuild | Execute a CodeBuild project with a privileged service role |
The common thread: any service that executes code and accepts a role via iam:PassRole is a potential escalation vector.
Understanding iam:CreatePolicyVersion
iam:CreatePolicyVersion allows a principal to create a new version of an existing managed IAM policy. AWS stores up to five versions per policy and lets you mark any version as the default. The permission iam:SetDefaultPolicyVersion promotes a version to active.
The Escalation Chain
Suppose a principal has a restrictive policy attached, but also holds iam:CreatePolicyVersion. The attack is straightforward:
# 1. Get the ARN of the policy attached to your user/role
aws iam list-attached-user-policies --user-name target-user
# 2. Create a new version that grants AdministratorAccess
aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/DevPolicy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]
}' \
--set-as-default
The --set-as-default flag makes the new version active immediately. The principal now has full * access.
If iam:SetDefaultPolicyVersion is not available separately, create-policy-version --set-as-default achieves both in one call.
The Variant: iam:CreatePolicyVersion on Another User’s Policy
The attack is even more impactful when the policy targeted belongs to an admin user or role. If a service account holds iam:CreatePolicyVersion with a wildcard resource, it can overwrite any managed policy in the account — including policies attached to admin roles used by humans or CI/CD pipelines.
Automated Discovery with Enumerate IAM
Enumerate IAM and PMapper are the standard tools for mapping escalation paths:
# PMapper: generate a full privilege escalation graph
pip install principalmapper
pmapper graph create --account 123456789012
pmapper analysis --output-type text
pmapper query "who can escalate privileges?"
PMapper models 20+ escalation paths, including all the PassRole pairings above, and outputs a graph showing which identities can reach admin.
Detection
CloudTrail events to monitor:
iam:CreatePolicyVersion— especially whensetAsDefault=trueiam:SetDefaultPolicyVersionon admin-scoped policieslambda:CreateFunctionorec2:RunInstanceswhere the passed role has admin-level accessiam:PassRolewhere the role ARN contains known privileged roles
Sample EventBridge rule (CloudWatch Logs Insights):
filter eventSource = "iam.amazonaws.com"
and eventName in ["CreatePolicyVersion", "SetDefaultPolicyVersion"]
and requestParameters.setAsDefault = "true"
| stats count(*) by userIdentity.arn, requestParameters.policyArn
Remediation
1. Constrain PassRole to specific roles:
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/lambda-execution-*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
The iam:PassedToService condition key restricts which service the role can be passed to — this significantly reduces the blast radius.
2. Restrict CreatePolicyVersion to specific policy ARNs, never wildcard.
3. Enable IAM Access Analyzer to surface policies with broad PassRole grants and flag external access.
4. Run PMapper quarterly as part of your IAM audit process. Many organizations discover escalation paths that were introduced months prior via incremental permission additions.
5. Enforce SCPs at the Organization level:
{
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/IAMAdminRole"
}
}
}
This SCP denies policy version management to everyone except a designated IAM admin role, regardless of what identity-level policies say.
Key Takeaways
iam:PassRole and iam:CreatePolicyVersion are force-multipliers. A principal with either permission and the right complementary permissions can reach admin without ever touching iam:*. Regular privilege escalation audits using tools like PMapper, combined with targeted CloudTrail alerting and restrictive SCPs, are the effective controls. Treating iam:PassRole as equivalent to iam:* in your risk model is not an overstatement.