Daily Cloud Blog • Cloud Exploit Series

Abusing AWS IAM Trust Policies for Privilege Escalation

Cloud Exploit Series • Daily Cloud Blog

Difficulty: Intermediate • Cloud: AWS • Focus: IAM / STS

What you’ll learn: How a single misconfigured role trust policy can let a low-priv identity assume a stronger role —
and exactly how to detect and prevent it.

Ethical Use Notice: This is for authorized testing, defensive validation, and education only.
Do not use against systems you do not own or explicitly have permission to assess.

In this post

  1. Executive overview
  2. Target architecture
  3. Attack assumptions
  4. Exploit walkthrough (lab validation)
  5. Detection strategy
  6. Mitigation strategy
  7. Terraform secure baseline
  8. Key takeaways

Executive overview

In AWS, permissions are only half the story. A role can have a perfect permission policy, but if its trust policy
(who is allowed to assume it) is too broad, you’ve created a privilege-escalation path.

  • Impact: Low-priv identity can become a high-priv role (data access, lateral movement, persistence).
  • Root cause: Overly permissive sts:AssumeRole trust relationships (often “temporary” shortcuts that become permanent).
  • Fix: Tight trust principals + conditions (OrgID, ExternalId, tags), plus guardrails (SCP) and detection (CloudTrail).

Target architecture

Typical enterprise pattern: teams deploy workloads with “service roles” and create “break-glass” or “admin automation” roles.
Someone adds a broad trust statement to “make integrations work”… and it becomes an escalation bridge.

[Low-Priv User/Role] --(sts:AssumeRole)--> [Target Role (Powerful)]
        |                                         |
        |                              [S3, KMS, Secrets, EC2, IAM...]
        |
   CloudTrail logs everything (if you’re watching)
    

The key point: Trust policy decides who can assume a role. Permission policy decides what that role can do once assumed.

Attack assumptions

  • Attacker has some AWS access (stolen keys, compromised workload, or an overly permissive internal account).
  • A target role exists with higher privilege than the attacker currently has.
  • The target role trust policy allows assumption by an overly broad principal (account-wide, wildcard, or weak conditions).

Exploit walkthrough (lab validation)

Step 1 — Identify role assumption paths

In defensive assessments, you’re looking for roles where the trust relationship is broader than intended.
Common smells:

  • Trust principal set to an entire account root: "AWS": "arn:aws:iam::111122223333:root"
  • Trusting multiple accounts without guard conditions
  • Trusting a role naming pattern without restricting tags / paths
  • Allowing assumption by identities that shouldn’t be able to “jump” privilege tiers

Misconfiguration example (too broad trust)

This trust policy allows any principal in Account 111122223333 to assume the role.
That’s often not what you want — especially for high-priv roles.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "sts:AssumeRole"
    }
  ]
}
    

Step 2 — Validate in an authorized lab

If your current identity has permission to call sts:AssumeRole (either explicitly or through broad IAM),
and the trust policy permits it, you can obtain temporary credentials for the target role.
In an assessment, this validates the escalation path.

# Authorized lab validation
aws sts get-caller-identity

# Attempt to assume the target role (example)
aws sts assume-role \
  --role-arn arn:aws:iam::444455556666:role/TargetPowerRole \
  --role-session-name dcb-cloud-exploit-series-01

# Use returned temporary credentials (set env vars) and re-check identity
aws sts get-caller-identity
    

Step 3 — Observe the privilege delta

The risk becomes real when the assumed role can do something your original identity could not.
Examples include:

  • Reading secrets (secretsmanager:GetSecretValue)
  • Decrypting data keys (kms:Decrypt)
  • Enumerating / modifying IAM (iam:* actions)
  • Modifying logging or guardrails (e.g., CloudTrail, Config)

Architect’s perspective: This misconfig usually happens when teams want cross-account automation quickly.
The trust relationship gets broadened, then never tightened.

Detection strategy

The good news: role assumption is noisy (in a good way). You can detect it reliably if you’re collecting CloudTrail properly.

CloudTrail events to watch

  • AssumeRole (STS) — primary signal
  • AssumeRoleWithWebIdentity — common in EKS/IRSA and OIDC patterns
  • AssumeRoleWithSAML — federated enterprise access

High-value detections

  • AssumeRole into privileged roles outside normal admin workflow
  • AssumeRole from unusual source IPs / user agents
  • AssumeRole followed by IAM changes, CloudTrail changes, or KMS decrypt spikes
  • Cross-account AssumeRole not tied to known automation principals
# Detection pseudo-logic (SIEM / analytics)
# IF eventSource=sts.amazonaws.com AND eventName=AssumeRole
# THEN enrich with:
#   - roleArn
#   - sourceIPAddress
#   - userIdentity.arn (who assumed)
#   - sessionContext attributes
# ALERT when:
#   - roleArn in "PrivilegedRoles" list AND caller not in "ApprovedPrincipals"
#   - cross-account assume not in allowlist
    

Mitigation strategy

1) Restrict the trusted principal

Don’t trust the whole account root unless you truly mean it.
Prefer trusting specific roles used for automation, with clear naming, paths, and ownership.

2) Add conditions that enforce intent

  • ExternalId for third-party or cross-account integrations
  • aws:PrincipalOrgID to restrict to your AWS Organization
  • aws:PrincipalArn allowlist (with care)
  • sts:RoleSessionName patterns for automation hygiene
  • Tags (ABAC) to require specific principal tags

3) Guardrail it with SCPs

In AWS Organizations, add SCPs that prevent creation of overly broad trust policies for roles in sensitive OUs/accounts.
This prevents the problem from reappearing.

4) Least privilege on who can call STS

Treat sts:AssumeRole as a privileged action.
Most identities should be able to assume only a minimal set of roles, not “any role anywhere.”

Terraform secure baseline

Below is a hardened trust policy example. It demonstrates three protections:
(1) trust a specific role principal, (2) restrict to your Org, (3) enforce a session name pattern.
Adjust to your environment.

data "aws_iam_policy_document" "assume_role_trust" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::111122223333:role/AutomationAssumerRole"]
    }

    condition {
      test     = "StringEquals"
      variable = "aws:PrincipalOrgID"
      values   = ["o-xxxxxxxxxx"]
    }

    condition {
      test     = "StringLike"
      variable = "sts:RoleSessionName"
      values   = ["dcb-*"]
    }
  }
}

resource "aws_iam_role" "target_power_role" {
  name               = "TargetPowerRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role_trust.json
}
    

Operational tip: Put privileged roles behind a break-glass workflow (ticket + approval + MFA),
and keep a short list of approved principals that can assume them.

Key takeaways

  • Trust policy defines who can become a role; permission policy defines what that role can do.
  • Account-root trust is a common “shortcut” that becomes a long-term escalation path.
  • Detect with CloudTrail (AssumeRole) + alert on privileged roles + unusual principals.
  • Prevent with restricted principals, strong conditions (OrgID/ExternalId/tags), and SCP guardrails.

Next in the Cloud Exploit Series:
EC2 Metadata Service Abuse (IMDSv1 vs IMDSv2)


Want More Offensive Cloud Research?

Subscribe to Daily Cloud Blog for deep-dive cloud attack path analysis,
secure Terraform patterns, and enterprise-ready defensive strategies.


Discover more from My Daily Cloud Blog

Subscribe to get the latest posts sent to your email.

Leave a comment

Trending