BIPI
BIPI

AWS IAM Privilege Escalation Paths a Pentester Will Find

Cloud Security

AWS IAM rarely fails through a single bad policy. It fails through chains of barely-noticed permissions that compose into administrator. This is what we look for and what to fix.

By Arjun Raghavan, Security & Systems Lead, BIPI · January 23, 2025 · 8 min read

#aws#iam#privilege-escalation#pentest

When we land in an AWS account during an authorized engagement, we rarely care about a single overpermissioned policy. We care about chains. A user with iam:PassRole plus ec2:RunInstances is one CLI invocation away from a privileged EC2 instance running with an admin role attached. A user with lambda:UpdateFunctionCode against a function bound to a powerful execution role is one zip upload away from arbitrary code under that role. The IAM model is composable, and so is the abuse.

How attackers find this

The first move is enumeration without tripping alarms. We pull the account authorization details, list policies attached to the current principal, and walk role trust documents looking for anything we can sts:AssumeRole into. Pacu's iam_privesc_scan automates the well-known paths, and Cloudsplaining renders the IAM exposure into a navigable report so we can prioritize.

  • iam:PassRole with ec2:RunInstances launches an instance and reads its role credentials from IMDSv2.
  • lambda:UpdateFunctionCode swaps the code of a function that already has a privileged execution role.
  • glue:CreateDevEndpoint spins up a Glue endpoint with a chosen role and an SSH key the attacker controls.
  • iam:CreateAccessKey on another user produces long-lived credentials for that identity.
  • iam:AttachUserPolicy or iam:PutUserPolicy directly grants AdministratorAccess.
  • cloudformation:CreateStack with a role passed via PassRole executes templates as that role.
  • datapipeline:CreatePipeline and codebuild:CreateProject have similar PassRole shaped abuses.

Twenty-plus paths exist in the public catalog. The shape repeats: a service that accepts a role, plus the iam:PassRole that lets the principal hand a higher-privileged role to that service. The Lambda and EC2 paths are the most common in real environments because both services are everywhere.

Methodology in practice

We map the blast radius before exploiting anything. For each candidate path, we identify the destination role, what it can do (SecurityAudit-style read, or full admin), and whether the path is reachable from our current credentials. We prefer paths that touch services already in heavy use because the noise floor masks our actions. A new EC2 instance in an account that launches hundreds per day is invisible. The same instance in a steady-state account stands out.

Detection

CloudTrail captures every IAM and STS call. The detections we recommend look at relationships, not single events. AssumeRole followed within seconds by RunInstances using the assumed role is a fingerprint. UpdateFunctionCode on a function whose role has iam:* is another. CreateAccessKey by a principal that has never created keys before is a third. GuardDuty catches some of this with the IAM finding family, but custom EventBridge rules pointed at high-value role names will catch what GuardDuty misses.

Remediation

  1. Run IAM Access Analyzer in policy generation mode against active principals to derive least-privilege policies from real CloudTrail usage.
  2. Apply permission boundaries to all human and CI principals so that even compromised credentials cannot escalate beyond the boundary.
  3. Add condition keys: iam:PassedToService restricts which services a role can be passed to, aws:PrincipalOrgID prevents cross-org abuse, aws:SourceVpc anchors actions to your network.
  4. Move CI/CD off long-lived keys to OIDC federation with GitHub Actions or GitLab so there is nothing to exfiltrate.
  5. Enable IMDSv2 with hop limit 1 on every EC2 to remove the SSRF-to-credentials primitive that makes RunInstances paths so cheap.
  6. Track iam:PassRole grants in a quarterly review; treat each grant as an admin-equivalent assertion until proven otherwise.

The fix is not removing iam:PassRole. The fix is binding it to the specific roles a principal is allowed to pass and the specific services those roles are allowed to be passed to. Boundaries plus conditions plus review is what shrinks the attack graph from twenty paths to zero.

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.