Adversaries may leverage cross-service Azure Data Explorer queries to exfiltrate sensitive information such as access tokens by exploiting external data functions. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential data exfiltration attempts using KQL queries.
KQL Query
let StringToSearch = @"\badx\s*\(";
let ExtractQueriedClusterAddress = @"([^\w]|^)adx\s*\(([^\)]*)\)";
LAQueryLogs
| where QueryText matches regex StringToSearch
| extend QueriedClusterAddress = extract_all(ExtractQueriedClusterAddress, dynamic([2]), QueryText)
| mv-expand QueriedClusterAddress to typeof(string)
| where isnotempty(QueriedClusterAddress)
| project TimeGenerated, AADEmail, QueriedClusterAddress, ResponseCode, QueryText, RequestTarget
id: 58b17f82-f594-4d36-9b78-4e4b03992708
name: Cross-service Azure Data Explorer queries
description: |
'Under specific circumstances, executing KQL queries can exfiltrate information like access tokens, regarding external data functions like adx().
This query tries to list executed KQL queries that used the adx() function and where an access token might have been exposed to.
Ref: https://docs.microsoft.com/azure/azure-monitor/logs/azure-monitor-data-explorer-proxy
Ref: https://securecloud.blog/2022/04/27/azure-monitor-malicious-kql-query/'
requiredDataConnectors:
- connectorId: AzureMonitor(Query Audit)
dataTypes:
- LAQueryLogs
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
let StringToSearch = @"\badx\s*\(";
let ExtractQueriedClusterAddress = @"([^\w]|^)adx\s*\(([^\)]*)\)";
LAQueryLogs
| where QueryText matches regex StringToSearch
| extend QueriedClusterAddress = extract_all(ExtractQueriedClusterAddress, dynamic([2]), QueryText)
| mv-expand QueriedClusterAddress to typeof(string)
| where isnotempty(QueriedClusterAddress)
| project TimeGenerated, AADEmail, QueriedClusterAddress, ResponseCode, QueryText, RequestTarget
Scenario: Scheduled data ingestion pipeline using Azure Data Explorer
Description: A legitimate scheduled job runs a KQL query to ingest data from Azure Blob Storage into Azure Data Explorer using the adx() function.
Filter/Exclusion: process.name == "azcopy" or process.name == "AzureBlobStorageIngestionJob"
Scenario: Admin task to query external data sources for troubleshooting
Description: An admin uses a KQL query with adx() to fetch logs from an external Azure Data Explorer instance to troubleshoot an issue.
Filter/Exclusion: user.name == "admin-troubleshooting" or user.group == "AzureAdmins"
Scenario: Automated report generation using KQL and external data functions
Description: A scheduled report generation tool runs a KQL query that uses adx() to pull data from another Azure Data Explorer instance for reporting.
Filter/Exclusion: process.name == "Power BI Report Server" or process.name == "ReportScheduler"
Scenario: Data migration between Azure Data Explorer instances
Description: A data migration tool runs a KQL query using adx() to transfer data between two Azure Data Explorer clusters.
Filter/Exclusion: process.name == "DataMigrationTool" or process.name == "AzureDataMigration"
Scenario: Security monitoring tool querying external data sources
Description: A security information and event management (SIEM) tool runs a KQL query using adx() to correlate logs from multiple Azure Data Explorer instances.
Filter/Exclusion: process.name == "SIEM-Tool" or process.name == "LogCorrelationService"