Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

User Can Escalate Privileges: Finding and Fixing IAM Escalation Paths in AWS

Learn how to detect and remediate IAM users who can escalate to admin in AWS, with CLI fixes, permissions boundaries, SCPs, and CI/CD policy gates.

TL;DR

This check flags IAM users holding permissions like iam:CreatePolicy or iam:AttachUserPolicy that let them quietly grant themselves admin access. Strip these permissions from human users, move policy management behind roles with approval, and audit for the known privilege escalation patterns.

An IAM user with a modest-looking permission set can sometimes hand themselves the keys to the entire account. That is the problem the User Can Escalate Privileges check looks for. It scans the effective permissions of your IAM users and surfaces anyone who can reach admin-level access through a chain of IAM actions, even if they were never granted AdministratorAccess directly.

This matters because privilege escalation is one of the most common ways a low-value credential turns into a full account compromise. A leaked access key for a "read-only" service account is bad. A leaked access key that can attach AdministratorAccess to itself is a breach.


What this check detects

The check inspects each IAM user's attached and inline policies, plus any group memberships, and looks for permissions that allow self-escalation. These are IAM and related actions that can be combined to grant new privileges. Common examples include:

  • iam:CreatePolicy and iam:CreatePolicyVersion — create or overwrite a policy with broader permissions
  • iam:AttachUserPolicy, iam:AttachGroupPolicy, iam:AttachRolePolicy — attach an existing admin policy
  • iam:PutUserPolicy, iam:PutGroupPolicy, iam:PutRolePolicy — write a new inline policy
  • iam:CreateAccessKey on another user — mint credentials for a more privileged identity
  • iam:UpdateAssumeRolePolicy combined with sts:AssumeRole — rewrite a role's trust policy and assume it
  • iam:PassRole paired with a service that runs code, such as ec2:RunInstances or lambda:CreateFunction

Note: Escalation is rarely a single permission. It is usually a chain. For example, iam:CreatePolicyVersion alone lets a user create a new default version of a managed policy that is already attached to them, instantly upgrading their own access without ever touching AttachUserPolicy.

The check does not just match on action names. It evaluates whether the resource and condition scoping actually constrains the action. A user with iam:AttachUserPolicy limited to a specific non-admin policy ARN is far less dangerous than one with Resource: "*".


Why it matters

IAM users are long-lived identities with static credentials. Unlike a role session that expires, an access key sits in a config file, a CI variable, or a developer laptop until someone rotates it. When that key can escalate, every place it lives becomes a path to account takeover.

Here is a realistic scenario. A developer is given a policy intended to let them manage their team's deployment pipeline. Someone adds iam:* scoped to the team's path to "make IAM changes easier." Months later, the developer's access key leaks through a committed .env file. An attacker runs:

aws iam create-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/team-deploy-policy \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}' \
  --set-as-default

That single command turns a scoped policy into full admin, and because the policy was already attached to the developer's user, the attacker inherits it immediately. No new attachment, no new user, nothing that looks unusual at a glance.

Danger: Privilege escalation paths are frequently invisible in the IAM console because the granted permissions look narrow. You have to evaluate the effect of the permissions, not their names. A user who "can only manage policies" can often become root-equivalent in one API call.

The business impact is straightforward: an attacker with admin can disable CloudTrail, delete logs, exfiltrate data from S3 and RDS, spin up crypto-mining fleets, or hold your account ransom. Escalation removes the blast-radius limits you thought you had.


How to fix it

The goal is to remove escalation-capable permissions from human and service users, and to move sensitive IAM management behind roles that require explicit assumption and approval.

1. Identify what the user can actually do

Start by listing the policies attached to the flagged user.

# Managed policies attached directly
aws iam list-attached-user-policies --user-name jdoe

# Inline policies on the user
aws iam list-user-policies --user-name jdoe

# Groups the user belongs to (group policies apply too)
aws iam list-groups-for-user --user-name jdoe

Then simulate the dangerous actions to confirm the escalation path is real and not blocked by a boundary or SCP.

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/jdoe \
  --action-names iam:CreatePolicyVersion iam:AttachUserPolicy iam:PassRole

2. Remove or scope the offending permissions

Warning: Removing IAM permissions can break automation that legitimately manages policies (for example, a CI pipeline that provisions roles). Check what the identity is used for before detaching anything, and stage the change outside peak deploy windows.

If the permission was granted through a custom inline policy, rewrite it with tight resource scoping and explicit denies on the escalation actions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPrivilegeEscalation",
      "Effect": "Deny",
      "Action": [
        "iam:CreatePolicyVersion",
        "iam:SetDefaultPolicyVersion",
        "iam:AttachUserPolicy",
        "iam:AttachGroupPolicy",
        "iam:AttachRolePolicy",
        "iam:PutUserPolicy",
        "iam:PutGroupPolicy",
        "iam:PutRolePolicy",
        "iam:CreateAccessKey",
        "iam:UpdateAssumeRolePolicy"
      ],
      "Resource": "*"
    }
  ]
}

Apply it as an explicit deny that overrides any allow:

aws iam put-user-policy \
  --user-name jdoe \
  --policy-name deny-privilege-escalation \
  --policy-document file://deny-escalation.json

If the user had an overly broad managed policy attached, detach it:

Danger: The command below changes a live user's permissions. Confirm the user is not your own break-glass identity and that an alternate admin path exists before you run it. Detaching the wrong policy can lock you out.

aws iam detach-user-policy \
  --user-name jdoe \
  --policy-arn arn:aws:iam::123456789012:policy/team-deploy-policy

3. Apply a permissions boundary

For users that legitimately need some IAM access, attach a permissions boundary so they can never grant themselves more than the boundary allows, even if their identity policy says otherwise.

aws iam put-user-permissions-boundary \
  --user-name jdoe \
  --permissions-boundary arn:aws:iam::123456789012:policy/developer-boundary

The boundary acts as a ceiling. A user can only escalate up to that ceiling, which neutralizes the most dangerous self-grant paths.

4. Move sensitive IAM work to assumable roles

Long-term: humans should not carry escalation permissions on a static user at all. Replace them with a role that requires MFA to assume and is granted just-in-time.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": { "aws:MultiFactorAuthPresent": "true" },
        "NumericLessThan": { "aws:MultiFactorAuthAge": "3600" }
      }
    }
  ]
}

How to prevent it from happening again

One-time cleanup does not last. New users and policies appear constantly, so you need guardrails that catch escalation before it reaches production.

Enforce permissions boundaries with an SCP

Use a Service Control Policy in AWS Organizations to require that any IAM user or role created in member accounts carries a permissions boundary. This stops anyone from creating an unbounded admin in the first place.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireBoundaryOnCreate",
      "Effect": "Deny",
      "Action": ["iam:CreateUser", "iam:CreateRole"],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/developer-boundary"
        }
      }
    }
  ]
}

Gate IAM changes in CI/CD

If you manage IAM with Terraform, run a policy-as-code check on every pull request. Here is a Checkov-style example using a custom OPA policy that fails the build when a user policy includes escalation actions on a wildcard resource.

# Run in CI before terraform apply
checkov -d ./infra --framework terraform \
  --external-checks-dir ./policies/iam-escalation

Tip: Lensix runs the account_escalation check continuously, so you catch escalation paths created through the console or click-ops, not just the ones in your Terraform. Pair the CI gate with a scheduled scan so manual changes do not slip through the cracks between deploys.

Alert on the escalation actions themselves

Create a CloudTrail metric filter and alarm on the high-risk API calls so you know within minutes if someone tries to use them.

aws logs put-metric-filter \
  --log-group-name cloudtrail-logs \
  --filter-name iam-escalation-attempts \
  --filter-pattern '{ ($.eventName = "CreatePolicyVersion") || ($.eventName = "AttachUserPolicy") || ($.eventName = "UpdateAssumeRolePolicy") }' \
  --metric-transformations metricName=IamEscalationAttempts,metricNamespace=Security,metricValue=1

Best practices

  • Prefer roles over users. Static access keys are the root of most escalation risk. Use IAM Identity Center or federation so humans get short-lived credentials and no permanent keys to leak.
  • Scope every IAM action by resource. If a user needs iam:AttachUserPolicy, restrict the Resource to specific user ARNs and the policy ARNs to a known safe set.
  • Always set a permissions boundary on identities that touch IAM. It is the single most effective control against self-escalation.
  • Run IAM Access Analyzer. Its unused-access findings help you strip permissions nobody actually exercises, shrinking the escalation surface.
  • Watch the quiet actions. iam:CreatePolicyVersion, iam:SetDefaultPolicyVersion, and iam:PassRole are easy to overlook and dangerous in combination. Treat them as admin-equivalent.
  • Review group memberships. A harmless-looking user can inherit escalation from a group policy. Audit the full effective permission set, not just direct attachments.

If an identity can change who is allowed to do what, treat it as admin. The permission names do not matter, the reachable end state does.

Fixing this check is not about one user. It is about removing the structural ability for low-trust identities to rewrite their own trust. Scope the permissions, enforce boundaries, gate the changes in CI, and let Lensix keep watch for the paths that creep back in.