open atlas
↑ Back to track
Cloud & Infra Security CLOUD · 01 · 04

IAM misconfigurations

The IAM misconfigs that turn one foothold into account takeover: iam:PassRole + a service that assumes any role is a privilege-escalation ladder, and a third party you let assume your role with no ExternalId is a confused deputy. Both are grants, not bugs.

CLOUD Senior ◷ 18 min
Level
FoundationsJuniorMiddleSenior

The pentester didn’t have admin. They had one leaked CI deploy key with a policy that read, to the team, like “can deploy our service.” What it actually granted was iam:PassRole on a wildcard plus lambda:CreateFunction. Forty minutes later they were root in the account — not by exploiting a CVE, but by using the permissions exactly as written: create a Lambda, pass it the admin role the account already had lying around, invoke it, read the credentials out of the environment. Every API call returned 200. CloudTrail logged each one as authorized, because each one was. The breach wasn’t a hole in IAM. It was IAM doing precisely what the policy told it to.

By the end of this lesson you’ll be able to spot the two IAM misconfigs that turn a single foothold into account takeover — privilege-escalation chains and the confused deputy — and name the one property of each that breaks the chain.

Why IAM misconfigurations are a category of their own

Earlier in this unit you saw the cloud identity model, least-privilege policy authoring, and how role assumption and federation work. This lesson is where those go wrong in production — not through exploitable code, but through grants that are individually defensible and collectively catastrophic.

That distinction is the whole game. A SQL injection is a bug: the code does something its author never intended. An IAM misconfiguration is a grant: the system does exactly what the policy says, and the policy says too much. Scanners that look for malformed input find nothing, because nothing is malformed. CloudTrail shows a clean 200 on every call, because every call was authorized. The damage is done entirely within the lines of the permission you wrote. This is why IAM review is read as reachability, not syntax: the question is never “is this policy valid?” — it’s “what is the transitive closure of everything this principal can reach?”

Two shapes dominate real incidents, and a senior learns to see both as paths, not statements: privilege escalation (a grant that lets a principal grant itself more) and the confused deputy (a trusted service that acts on attacker-supplied input with its own authority, not the attacker’s).

Privilege escalation: when a permission lets you grant yourself more

Privilege escalation is the misconfig where a principal holds a permission that, used as designed, lets it acquire permissions it was never given. The single most important one to recognize is iam:PassRole.

PassRole exists for a legitimate reason: when you create a Lambda function, an EC2 instance, or an ECS task, something has to attach an execution role so the workload can call AWS APIs. PassRole is the permission to hand a role to a service. The trap is the resource scope. A policy that reads iam:PassRole on Resource: "*" doesn’t mean “pass your own narrow role” — it means pass any role in the account, including the admin role. Pair that with any service that runs code (lambda:CreateFunction + lambda:InvokeFunction, or ec2:RunInstances), and you have a complete ladder:

PassRole is the headline, but the family is large, and the senior skill is recognizing the pattern rather than memorizing a list. Any of these is a one-step escalation when granted on a wildcard:

PermissionLooks like it grantsActually lets youThe scoping that stops it
iam:PassRole + run-code”Deploy a Lambda”Run code as any role, incl. adminScope to one role ARN + iam:PassedToService
iam:CreatePolicyVersion”Edit our policies”Rewrite an attached policy to allow everythingDeny on your own boundary; permissions boundary
iam:AttachUserPolicy”Manage team access”Attach AdministratorAccess to yourselfPermissions boundary caps the effective grant
lambda:UpdateFunctionCode”Ship a hotfix”Replace code in a function that has a high-priv roleSeparate deploy identity from privileged exec roles

The defense isn’t “ban these actions” — they’re load-bearing. It’s resource scoping plus a permissions boundary. Scope PassRole to the exact role ARN the workload needs; never a wildcard. Then wrap any identity that can mint or attach permissions in a permissions boundary — a ceiling that the principal cannot exceed no matter what policy it grants itself. The boundary is what turns AttachUserPolicy from “attach admin” into “attach anything, capped at the boundary.”

The confused deputy: when a trusted service acts on attacker input

The second shape is subtler because the attacker never touches your account directly. A confused deputy is a privileged component tricked into misusing its own authority on behalf of a less-privileged caller. The classic cloud case: you grant a third-party SaaS (a monitoring tool, a backup vendor) permission to assume a role in your account so it can read your data. The vendor assumes one role to serve all its customers.

Here’s the trap. The vendor’s role-assumption call needs only the target account id and role name — both of which a malicious other customer of that same vendor can guess. Customer B asks the vendor to “monitor account 1234 / role MyAuditRole.” The vendor, as designed, assumes that role. Now the vendor — the deputy — is reading your account on Customer B’s behalf. The vendor wasn’t compromised; it did exactly its job. It was confused about whose request it was serving.

The fix is a shared secret bound into the trust relationship: an ExternalId. When you set up the vendor, you agree on a per-tenant secret and put "sts:ExternalId": "your-unique-value" as a condition in the role’s trust policy. Now the vendor must present your ExternalId to assume your role — and Customer B, who can guess your account id and role name, cannot guess a secret they were never given. The same principle generalizes: a confused deputy is defeated by binding the authorization decision to the original requester’s identity, not just to the deputy’s. In your own services, the equivalent is passing and checking the end-user’s identity through to the resource, never letting a privileged backend act on a raw, attacker-influenced target.

Why this works

Why isn’t “the vendor’s IAM role is read-only” enough on its own? Because read-only across the wrong account is still a breach — the confused deputy doesn’t escalate the deputy’s privilege, it redirects the deputy’s existing privilege at a victim it was never meant to touch. ExternalId works at a different layer than the permission scope: scope answers “what can the deputy do?”, ExternalId answers “for whom is it allowed to do it?”. You need both. A read-only role with no ExternalId still leaks every customer’s data to every other customer who can name an account.

How a senior reviews an IAM change: think in blast radius

The unifying mental model is blast radius: if this exact principal’s long-lived credential leaked to an attacker tomorrow, what is the transitive closure of everything they could reach? Not “what is this role for” — what can it become. A deploy key that can pass any role has a blast radius of the entire account, even though its job is to push one service.

That reframing changes how you read policies. You stop scanning for syntax errors and start tracing reachability: does any grant let this principal acquire another identity (escalation)? Does any trust policy let an outside party borrow this account’s authority without proving they are the intended party (confused deputy)? The controls map cleanly onto those two questions — least-privilege resource scoping and permissions boundaries cap what a principal can become; ExternalId and per-request identity propagation cap whose behalf a deputy may act on. Default-deny is the floor under both: every grant starts at “no” and is widened only to a named resource for a named reason.

Pick the best fit

A CI deploy role needs to create Lambda functions and attach an execution role to them. Security review flags iam:PassRole scoped to a wildcard resource. Pick the correct remediation.

Quiz

Why do IAM privilege-escalation and confused-deputy attacks usually evade input-scanning tools and show clean CloudTrail logs?

Quiz

A third-party backup vendor assumes a role in your account. The role is read-only and scoped tightly. Another customer of that vendor supplies your account id and role name and gets the vendor to read your buckets. What was missing?

Order the steps

Order the steps of the PassRole privilege-escalation chain, from the initial foothold to account takeover:

  1. 1 Attacker holds a low-priv key with iam:PassRole on a wildcard resource
  2. 2 Calls lambda:CreateFunction to make a new function
  3. 3 Passes the account's admin role as the function's execution role
  4. 4 Invokes the function, which now runs with admin privileges
  5. 5 Reads credentials / acts as admin — full account takeover
Recall before you leave
  1. 01
    Walk through the iam:PassRole privilege-escalation chain and name the single scoping change that breaks it.
  2. 02
    Explain the cross-account confused-deputy attack against a third-party vendor and why a read-only, tightly-scoped role isn't sufficient without an ExternalId.
Recap

IAM misconfigurations are a category apart from ordinary bugs: the system does exactly what the policy says, the policy just says too much, so input scanners find nothing and CloudTrail shows a clean 200 on every step. Two shapes drive real breaches. Privilege escalation is a grant that lets a principal grant itself more — the canonical case is a wildcard iam:PassRole paired with the ability to run code, which lets a low-privilege key create a Lambda, pass it the account’s admin role, invoke it, and own the account; the fix is resource-scoping PassRole to one ARN plus a permissions boundary as a ceiling. The confused deputy is a trusted service tricked into using its own authority for an attacker — a third-party vendor that assumes a role for every customer can be told to read your account by anyone who knows your account id and role name; the fix is an sts:ExternalId, a per-tenant secret bound into the trust policy that the attacker can’t supply. Both reduce to one senior reflex: review every IAM change by its blast radius — if this credential leaked tomorrow, what is the transitive closure of everything it could become or be aimed at? Scope what a principal can become; bind whose behalf a deputy may act on; default-deny under both.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 7 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.