Detection rules › Kusto

User impersonation by Identity Protection alerts

Severity
medium
Time window
1d
Group by
AlertName, AlertSeverity, AlertTime, ProductName, SourceIpAddress, ipAddress
Source
github.com/Azure/Azure-Sentinel

'This detection focuses on identifying user-related events involving IAM roles, groups, user access, and password changes. It examines instances where the user's IP address matches and alerts generated by Identity Protection share the same IP address. The analysis occurs within a time window of 1 hour, helping to flag potential cases of user impersonation.'

MITRE ATT&CK coverage

TacticTechniques
Privilege EscalationT1134 Access Token Manipulation

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

id: 11c3d541-5fa5-49df-8218-d1c98584473b
name: User impersonation by Identity Protection alerts
description: |
  'This detection focuses on identifying user-related events involving IAM roles, groups, user access, and password changes. It examines instances where the user's IP address matches and alerts generated by Identity Protection share the same IP address. The analysis occurs within a time window of 1 hour, helping to flag potential cases of user impersonation.'
severity: Medium
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert (IPC)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1134
query: |
 // Retrieve SecurityAlerts generated within the last day
  SecurityAlert 
  // Filter alerts for Azure Active Directory Identity Protection and High severity
  | where ProductName has "Azure Active Directory Identity Protection"
  | where AlertSeverity == "High"
  // Extract IP address entities from the 'Entities' field
  | extend ipAddress = extract(@'\b(?:\d{1,3}\.){3}\d{1,3}\b', 0, Entities)
  // Filter out alerts without IP address entities
  | where isnotempty(ipAddress)
  // Summarize entities per unique combination of attributes
  | summarize make_set(Entities)
      by
      AlertTime = TimeGenerated,
      ipAddress,
      AlertName,
      ProductName,
      AlertSeverity
  // Perform an inner join with AWS CloudTrail events
  | join kind=inner (
      AWSCloudTrail
      | where isempty(ErrorMessage)
      | extend UserType = tostring(parse_json(RequestParameters).userType) 
      | where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile") 
      | summarize
          make_set(RequestParameters),
          make_set(ResponseElements)
          by
          SourceIpAddress,
          UserIdentityArn,
          UserIdentityType,
          EventName,
          EventTime = TimeGenerated
      )
      on $left.ipAddress == $right.SourceIpAddress  
  // Filter results based on temporal correlation
  | where AlertTime between ((EventTime - 1h) .. (EventTime + 1h))
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
   AWSUser: UserIdentityArn
   AlertIp : ipAddress
   AlertName: AlertName
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProductName has "Azure Active Directory Identity Protection"

Stage 3: where

| where AlertSeverity == "High"

Stage 4: extend

| extend ipAddress = extract(@'\b(?:\d{1,3}\.){3}\d{1,3}\b', 0, Entities)

Stage 5: where

| where isnotempty(ipAddress)

Stage 6: summarize

| summarize make_set(Entities)
     by
     AlertTime = TimeGenerated,
     ipAddress,
     AlertName,
     ProductName,
     AlertSeverity

Stage 7: join

| join kind=inner (
     AWSCloudTrail
     | where isempty(ErrorMessage)
     | extend UserType = tostring(parse_json(RequestParameters).userType) 
     | where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile") 
     | summarize
         make_set(RequestParameters),
         make_set(ResponseElements)
         by
         SourceIpAddress,
         UserIdentityArn,
         UserIdentityType,
         EventName,
         EventTime = TimeGenerated
     )
     on $left.ipAddress == $right.SourceIpAddress

Stage 8: where

| where AlertTime between ((EventTime - 1h) .. (EventTime + 1h))

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
AlertSeverityeq
  • High transforms: cased
ErrorMessageis_null
  • (no value, null check)
EventNamein
  • AddUserToGroup
  • ChangePassword
  • CreateAccessKey
  • CreateGroup
  • CreateRole
  • CreateUser
  • CreateVirtualMFADevice
  • DeleteAccessKey
  • DeleteGroup
  • DeleteLoginProfile
  • DeleteRole
  • DeleteUser
  • RemoveUserFromGroup
ProductNamematch
  • Azure Active Directory Identity Protection transforms: term
ipAddressis_not_null
  • (no value, null check)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
AlertNamesummarize
AlertSeveritysummarize
AlertTimesummarize
ProductNamesummarize
ipAddresssummarize