Detection rules › Kusto
High-Risk Cross-Cloud User Impersonation
This detection focuses on identifying high-risk cross-cloud activities and sign-in anomalies that may indicate potential security threats. The query starts by analyzing Microsoft Entra ID Signin Logs to pinpoint instances where specific applications, risk levels, and result types align. It then correlates this information with relevant AWS CloudTrail events to identify activities across Azure and AWS environments.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry coverage
Rules detecting the same action
These rules filter on the same operation.
- AWS IAM Backdoor Users Keys (Sigma)
- AWS IAM Create User via Assumed Role on EC2 Instance (Elastic)
- AWS IAM Credentials Added to a Bedrock API Key Phantom User (Elastic)
- AWS IAM Failure Group Deletion (Splunk)
- AWS IAM Group Creation (Elastic)
- AWS IAM Group Deletion (Elastic)
- AWS IAM S3Browser User or AccessKey Creation (Sigma)
- AWS IAM Sensitive Operations via Lambda Execution Role (Elastic)
Rule body
id: f4a28082-2808-4783-9736-33c1ae117475
name: High-Risk Cross-Cloud User Impersonation
description: |
'This detection focuses on identifying high-risk cross-cloud activities and sign-in anomalies that may indicate potential security threats. The query starts by analyzing Microsoft Entra ID Signin Logs to pinpoint instances where specific applications, risk levels, and result types align. It then correlates this information with relevant AWS CloudTrail events to identify activities across Azure and AWS environments.'
severity: Medium
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1134
- T1078.002
- T1078.004
query: |
// Retrieve Azure AD SigninLogs within the last day
SigninLogs
// Filter for specific AppDisplayNames, ResultType, and Risk Levels
| where AppDisplayName in ("Azure Portal", "ADFS Trust", "Microsoft Azure PowerShell")
and RiskLevelAggregated == "high"
and RiskLevelDuringSignIn == "high"
// Summarize AppDisplayNames by relevant attributes
| extend Result = iff(ResultType == 0, "Successful Signin", "Failed Signin")
| summarize make_set(AppDisplayName)
by
IPAddress,
signInTime=TimeGenerated,
UserPrincipalName,
RiskEventTypes,
RiskEventTypes_V2
// Inner join with AWS CloudTrail events
| join kind=inner (
AWSCloudTrail
| where isempty(ErrorMessage)
| where EventSource in ("iam.amazonaws.com", "identitystore.amazonaws.com", "workmail.amazonaws.com", "workdocs.amazonaws.com")
// List of AWS event names
| where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile", "CreateOrganization", "SetDefaultMailDomain", "SetMailUserDetails", "CreateMailUser", "ResetPassword", "RegisterToWorkMail", "DisableMailUsers", "EnableMailUsers", "DeleteServiceSpecificCredential", "CreateServiceSpecificCredential", "UpdateAccountEmailAddress", "DeleteGroupPolicy", "UploadServerCertificate")
// Summarize relevant attributes
| summarize make_set(RequestParameters), make_set(ResponseElements)
by
SourceIpAddress,
UserIdentityArn,
UserIdentityType,
EventName,
EventTime=TimeGenerated,
EventSource
)
on $left.IPAddress == $right.SourceIpAddress
// Calculate time difference in hours between AWS event and Azure sign-in
| extend timedef = datetime_diff("hour", EventTime, signInTime)
// Filter for time differences within a certain range
| where timedef between (0 .. 8)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
AwsUser: UserIdentityArn
RiskEventTypes: RiskEventTypes
AzureUser: UserPrincipalName
AWSEventName: EventName
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
SigninLogs
Stage 2: where
| where AppDisplayName in ("Azure Portal", "ADFS Trust", "Microsoft Azure PowerShell")
and RiskLevelAggregated == "high"
and RiskLevelDuringSignIn == "high"
Stage 3: extend
| extend Result = iff(ResultType == 0, "Successful Signin", "Failed Signin")
Result =if
ResultType == 0"Successful Signin"else
"Failed Signin"Stage 4: summarize
| summarize make_set(AppDisplayName)
by
IPAddress,
signInTime=TimeGenerated,
UserPrincipalName,
RiskEventTypes,
RiskEventTypes_V2
Stage 5: join
| join kind=inner (
AWSCloudTrail
| where isempty(ErrorMessage)
| where EventSource in ("iam.amazonaws.com", "identitystore.amazonaws.com", "workmail.amazonaws.com", "workdocs.amazonaws.com")
| where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile", "CreateOrganization", "SetDefaultMailDomain", "SetMailUserDetails", "CreateMailUser", "ResetPassword", "RegisterToWorkMail", "DisableMailUsers", "EnableMailUsers", "DeleteServiceSpecificCredential", "CreateServiceSpecificCredential", "UpdateAccountEmailAddress", "DeleteGroupPolicy", "UploadServerCertificate")
| summarize make_set(RequestParameters), make_set(ResponseElements)
by
SourceIpAddress,
UserIdentityArn,
UserIdentityType,
EventName,
EventTime=TimeGenerated,
EventSource
)
on $left.IPAddress == $right.SourceIpAddress
Stage 6: extend
| extend timedef = datetime_diff("hour", EventTime, signInTime)
Stage 7: where
| where timedef between (0 .. 8)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppDisplayName | in |
| field:"Application" kind:in |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
EventSource | in |
| field:"aws::eventSource" kind:in |
RiskLevelAggregated | eq |
| field:"RiskLevelAggregated" kind:eq value:"high" |
RiskLevelDuringSignIn | eq |
| field:"RiskLevelDuringSignIn" kind:eq value:"high" |
timedef | ge |
| field:"timedef" kind:ge value:"0" |
timedef | le |
| field:"timedef" kind:le value:"8" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPAddress | summarize |
RiskEventTypes | summarize |
RiskEventTypes_V2 | summarize |
UserPrincipalName | summarize |
signInTime | summarize |
timedef | extend |