Frontend Labs
⋯

Chapter 6 — Cloud IAM in depth: policy evaluation and privilege-escalation paths

In one sentence

An IAM misconfiguration is a privilege-escalation path when a principal has a permission that lets it grant itself, or assume, more permission — and finding every such edge in an account is a graph problem with well-known primitives.

Where we are

Chapter 5 showed the identity plane. This chapter is the offensive and defensive core of cloud security: how a low-privileged principal becomes account-admin through a chain of individually-reasonable permissions.

How it works

AWS policy evaluation, precisely (commit this to memory):

   1. Start: implicit DENY.
   2. Is there an explicit DENY that matches? (in ANY of: SCP, identity
      policy, resource policy, permission boundary, session policy, VPC
      endpoint policy)  -> DENY. Nothing overrides an explicit deny.
   3. SCP: for a member account, the action must be allowed by SCPs.
      (Management account is not restricted by SCPs.)
   4. Resource-based policy: an Allow here can grant access even with no
      identity policy (same account). Cross-account: need Allow on BOTH
      sides.
   5. Identity-based policy: an Allow here grants access (subject to the
      rest).
   6. Permission boundary: if attached, the action must ALSO be allowed by
      it (it's a ceiling, not a grant).
   7. Session policy: if present (AssumeRole with --policy), must ALSO
      allow.
   FINAL: allowed only if some Allow applies AND no Deny applies AND every
   applicable ceiling (SCP, boundary, session) also allows.

The privilege-escalation primitives (AWS — the classic set; each is an edge "principal with permission X -> effectively admin or another role"):

   GRANT YOURSELF MORE (on your own user/role):
     iam:CreatePolicyVersion (+ SetAsDefault)   -> rewrite an attached policy
     iam:SetDefaultPolicyVersion                -> roll to an old permissive version
     iam:AttachUserPolicy / AttachRolePolicy    -> attach AdministratorAccess
     iam:PutUserPolicy / PutRolePolicy          -> inline an allow-* policy
     iam:AddUserToGroup                         -> join an admin group
     iam:CreateAccessKey (on another user)      -> steal a more-privileged user
     iam:CreateLoginProfile / UpdateLoginProfile-> set a console password on a
                                                   privileged user
     iam:UpdateAssumeRolePolicy                 -> make a privileged role trust you

   PASS A ROLE TO A COMPUTE SERVICE YOU CONTROL (iam:PassRole + ...):
     + ec2:RunInstances                         -> launch an instance with an
                                                   admin role, read its creds
     + lambda:CreateFunction + Invoke (or an
       event-source mapping)                    -> run code as an admin role
     + glue:CreateDevEndpoint / sagemaker /
       cloudformation / datapipeline / ecs      -> same idea, different service
     lambda:UpdateFunctionCode (existing fn
       with a good role)                        -> overwrite its code

   ASSUME:
     sts:AssumeRole where a role's trust policy is `Principal: *` or
       over-broad  -> just assume it

GCP equivalents:

   iam.serviceAccounts.actAs + (compute.instances.create /
     cloudfunctions.create / run.services.create / ...)  -> run as a
     more-privileged service account (the default Compute SA is Editor!)
   iam.serviceAccounts.getAccessToken / signJwt / signBlob  -> mint tokens
     for another SA directly
   iam.serviceAccountKeys.create  -> long-lived key for another SA
   resourcemanager.projects.setIamPolicy (or iam.roles.update)  -> grant
     yourself Owner
   deploymentmanager / cloudbuild jobs run as a Google-managed SA that is
     often project Editor  -> submit a job that escalates

Azure equivalents:

   Microsoft.Authorization/roleAssignments/write (Owner or User Access
     Administrator)  -> assign yourself Owner
   Assign a privileged MANAGED IDENTITY to a VM / automation account you
     control, then pull its token from IMDS
   Entra: Application Administrator / Cloud Application Administrator ->
     add a secret to a privileged service principal -> auth as it
   Entra: Privileged Role Administrator -> grant yourself Global Admin
   VM run-command / custom-script-extension -> local exec -> MI token

Defence:

   [ ] NO wildcards on iam:* actions in identity policies. Especially not
       iam:PassRole with Resource:* -- scope PassRole to specific role ARNs
       and add an iam:PassedToService condition.
   [ ] Permission boundaries on every non-admin human and CI role -> caps
       the ceiling so even a privesc primitive can't exceed the boundary.
   [ ] SCPs that deny: iam:CreateUser, iam:CreateAccessKey (outside a
       break-glass path), cloudtrail:StopLogging, disabling GuardDuty/
       Config, leaving the org, modifying the log-archive bucket, using
       regions you don't operate in, root actions.
   [ ] Role trust policies: never `Principal: "*"`; use exact account/role
       ARNs + `sts:ExternalId` for third parties (confused-deputy).
   [ ] Analyse continuously: run PMapper / cloudsplaining / Access Analyzer
       "unused access" in CI; fail on new privesc edges.
   [ ] Prefer permission sets via IAM Identity Center (SSO) over IAM users;
       zero standing admin (JIT elevation).

Worked example

A three-hop escalation in a sandbox, and the one condition that kills it.

   START: you have creds for `ci-deployer`, intended to deploy Lambda.
   Its policy: Allow lambda:*, iam:PassRole on Resource "*".

   HOP 1  enumerate: `aws iam list-roles` -> find `ops-admin-role`
          (AdministratorAccess), trusted by lambda.amazonaws.com.
   HOP 2  create a function passing that role:
            aws lambda create-function --function-name x --runtime python3.12
              --role arn:aws:iam::ACCT:role/ops-admin-role
              --handler x.h --zip-file fileb://x.zip
          (x.zip: a handler that calls `boto3` to attach
           AdministratorAccess to `ci-deployer`, or just returns STS creds)
   HOP 3  invoke it: `aws lambda invoke --function-name x out.json`
          -> code runs AS ops-admin-role -> you are now account admin.

   THE KILL: change `ci-deployer`'s policy so PassRole is
     Resource: "arn:aws:iam::ACCT:role/lambda-exec-*"  AND
     Condition: { StringEquals: { "iam:PassedToService":
       "lambda.amazonaws.com" } }
   Now `ci-deployer` can only pass narrowly-named exec roles to Lambda,
   and `ops-admin-role` (not matching the name pattern) can't be passed.
   Hop 2 fails with AccessDenied. Path broken with a resource scope + one
   condition. Re-run PMapper -> the edge is gone.

Real-world scenario: a real cross-tenant privilege-escalation CVE

   In 2021, Wiz Research disclosed "ChaosDB": a chain of vulnerabilities
   in Azure Cosmos DB's built-in Jupyter Notebook feature (Notebooks)
   that let an attacker, with no prior access to a victim's account at
   all, obtain the PRIMARY KEYS of ARBITRARY OTHER CUSTOMERS' Cosmos DB
   instances -- full read/write/delete access to their databases.
   The root cause was a misconfiguration in how the Notebooks feature
   handled certificate validation and container isolation, letting a
   researcher escape their own container and reach a management
   interface that (this chapter's exact theme) had FAR TOO MUCH implicit
   trust in "whatever called this API is authorized for whatever it
   asks." Microsoft's own response included rotating a very large number
   of customer keys as the remediation -- itself a real demonstration of
   why Chapter 44's "can you actually rotate this key without an outage"
   question matters at Azure's own scale, not just yours.
   THE LESSON FOR THIS CHAPTER: this was not a leaked credential or a
   phished admin -- it was a genuine PRIVILEGE-ESCALATION PRIMITIVE built
   into a managed service's own feature surface, discovered by external
   researchers rather than the vendor. It's a useful reminder that
   Ch 6's privesc-hunting discipline (continuous graphing, not a one-time
   audit) applies to what your CLOUD PROVIDER exposes to you, not only to
   the IAM policies you author yourself.

Practice (75 min) — sandbox account only

  1. Deploy IAM Vulnerable (BishopFox) or a CloudGoat IAM scenario into your sandbox.
  2. With the provided low-priv creds, escalate to admin. Do it two different ways (a "grant yourself" primitive and a "PassRole + compute" primitive).
  3. Run cloudsplaining against get-account-authorization-details output and PMapper (pmapper graph create then pmapper query "who can do iam:PutUserPolicy with *" and pmapper argquery --preset privesc). Confirm the tools find the paths you used.
  4. Apply the fixes: scope PassRole, add iam:PassedToService, attach a permission boundary, add SCP denies. Re-run PMapper — paths gone.
  5. Add a GuardDuty detector; re-run one escalation; note which finding fires (PrivilegeEscalation:IAMUser/AdministrativePermissions) and its latency.
  6. Tear down (cloudgoat destroy / terraform destroy); check the budget.

Common mistakes

  • iam:PassRole with Resource: "*". The single most common cloud privesc enabler. Always scope it and add iam:PassedToService.
  • Trust policies with Principal: "*" or a bare account root without a condition — anyone in that account (or anyone, with *) can assume it.
  • Relying on "they only have lambda:*" / "just iam:Get*". Read permissions enable enumeration; a single write primitive completes the chain.
  • No permission boundaries. Without a ceiling, one privesc primitive is game over. With one, the blast radius is capped.
  • Analysing IAM once. Policies drift every sprint. Put PMapper/cloudsplaining in CI.
  • Forgetting the management account. SCPs don't restrict it — nothing should run there, and access to it should be break-glass only.

Check yourself

  1. Recite AWS policy evaluation order. What can never be overridden?
  2. Give three "grant yourself more" privesc primitives and three "PassRole + service" ones.
  3. What two things scope iam:PassRole safely?
  4. What does a permission boundary do, and why does it blunt privesc even when a primitive exists?
  5. Name the GCP permission that is the rough equivalent of iam:PassRole, and why the default Compute service account makes it dangerous.
  6. Which account in an AWS org is not restricted by SCPs, and what follows from that?

Further reading

  • Primary: Rhino Security Labs, "AWS IAM Privilege Escalation – Methods and Mitigation" (Spencer Gietzen) — the canonical primitive list; and the aws_escalate tool.
  • Tools: PMapper (NCC Group), Cloudsplaining (Salesforce), AWS IAM Access Analyzer (external access + unused access + policy validation), iamlive (generate least-privilege policies from observed calls).
  • GCP: "Privilege Escalation in GCP" (Rhino Security Labs / GCP_IAM_ Privilege_Escalation repo); Google's "IAM securely" best-practices.
  • Azure: "Azure Privilege Escalation" write-ups (XPN, SpecterOps AzureHound/BloodHound Azure edges); MicroBurst tooling.
  • Practice: flaws2.cloud (attacker + defender tracks), cloudgoat, IAM Vulnerable, pwnedlabs.io.

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36