Lambda PassRole Wildcards Build Their Own Admin Role
A Lambda execution role granting iam:PassRole on Resource: "*" without an iam:PassedToService condition lets compromised function code pass any account role to EC2, ECS, or Glue. Attackers with mere code execution pivot straight to AdministratorAccess-level compute, and CloudTrail logs look like routine service calls.
At a glance
- Unsafe setting
- iam:PassRole Allow with Resource: "*" and no iam:PassedToService condition on Lambda execution role
- Failure trigger
- Compromised function code (dependency, injected payload, deserialisation bug) calls PassRole against a privileged role ARN
- Blast radius
- Attacker launches EC2, ECS, or Glue resources inheriting a passed admin-level role, compromising the entire AWS account.
- Recommended control
- Scope PassRole to named ARNs with iam:PassedToService condition; enforce via permission boundaries and org-wide SCP denying condition-less PassRole.
Fix commands and configuration
{"Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::123456789012:role/glue-etl-runtime", "Condition": {"StringEquals": {"iam:PassedToService": "glue.amazonaws.com"}}}iam:PassRoleiam:PassedToServiceThe Trap
An IAM policy attached to a Lambda execution role that grants iam:PassRole with Resource: "*" and no iam:PassedToService condition. The function can pass control of any role in the account to any service that accepts a role parameter, regardless of what that role was designed to do.
The Default State
Serverless scaffolds and CDK constructs that orchestrate Step Functions, Glue jobs, or ECS tasks from a Lambda need to pass a role to the downstream service, so the generated policy statement is written once, broadly, and never revisited: {"Effect": "Allow", "Action": "iam:PassRole", "Resource": "*"}. Developers add it to unblock a deploy, the pipeline passes, and the statement sits unreviewed because no security tool flags PassRole the way it flags AdministratorAccess.
The Blast Radius
An attacker who gets code execution inside the function, via a poisoned npm or PyPI dependency, an injected event payload, or a deserialisation bug, does not need to escalate their own IAM identity. They call iam:PassRole against the ARN of your CI/CD deploy role, your CloudFormation execution role, or a role with AdministratorAccess, then launch an EC2 instance, ECS task, or Glue job using that borrowed identity. The new compute resource inherits full permissions of the passed role, and the attacker pivots straight out of a sandboxed function into whatever that role can touch, often the entire account. Because the Lambda’s own execution role never changed, CloudTrail shows a legitimate-looking service call, not a privilege escalation event, so detection lags for days.
The Lead Mechanic Fix
Scope the statement to named role ARNs and pin the destination service with a condition key: {"Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::123456789012:role/glue-etl-runtime", "Condition": {"StringEquals": {"iam:PassedToService": "glue.amazonaws.com"}}}. Never let a function pass a role more privileged than its own execution role; enforce that with a permission boundary attached to every role the function is allowed to pass. At the organisation level, add an SCP denying iam:PassRole where the iam:PassedToService condition key is absent, closing the wildcard path account-wide regardless of what individual teams write into Terraform.