Correlate Unfamiliar sign-in properties atypical travel alerts
| Id | a3df4a32-4805-4c6d-8699-f3c888af2f67 |
| Rulename | Correlate Unfamiliar sign-in properties & atypical travel alerts |
| Description | The combination of an Unfamiliar sign-in properties alert and an Atypical travel alert about the same user within a +10m or -10m window is considered a high severity incident. |
| Severity | High |
| Tactics | InitialAccess |
| Techniques | T1078 |
| Required data connectors | AzureActiveDirectoryIdentityProtection BehaviorAnalytics |
| Kind | Scheduled |
| Query frequency | 1d |
| Query period | 14d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID Protection/Analytic Rules/CorrelateIPC_Unfamiliar-Atypical.yaml |
| Version | 1.0.8 |
| Arm template | a3df4a32-4805-4c6d-8699-f3c888af2f67.json |
// We can use this configuration TimeDeltaInMinutes if you want to chnage the time window that we try to match the alerts
let TimeDeltaInMinutes = 10;
let Alert_UnfamiliarSignInProps =
SecurityAlert
| where TimeGenerated > ago(1d)
| where ProductName =~ "Azure Active Directory Identity Protection"
| where AlertName =~ "Unfamiliar sign-in properties"
| mv-expand Entity = todynamic(Entities)
| where Entity.Type =~ "account"
| extend AadTenantId = tostring(Entity.AadTenantId)
| extend AadUserId = tostring(Entity.AadUserId)
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserName = AccountDisplayName
| extend UserAccount = AccountUPN
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
| extend Alert_UnfamiliarSignInProps_Time = TimeGenerated
| extend Alert_UnfamiliarSignInProps_Name = AlertName
| extend Alert_UnfamiliarSignInProps_Severity = AlertSeverity
| project AadTenantId, AadUserId, AccountTenantId, AccountObjectId, Alert_UnfamiliarSignInProps_Name, Alert_UnfamiliarSignInProps_Severity, Alert_UnfamiliarSignInProps_Time, UserAccount, UserName
;
let Alert_AtypicalTravels =
SecurityAlert
| where TimeGenerated > ago(1d)
| where ProductName =~ "Azure Active Directory Identity Protection"
| where AlertName =~ "Atypical travel"
| mv-expand Entity = todynamic(Entities)
| where Entity.Type =~ "account"
| extend AadTenantId = tostring(Entity.AadTenantId)
| extend AadUserId = tostring(Entity.AadUserId)
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserName = AccountDisplayName
| extend UserAccount = AccountUPN
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
| extend Alert_AtypicalTravels_Time = TimeGenerated
| extend Alert_AtypicalTravels_Name = AlertName
| extend Alert_AtypicalTravels_Severity = AlertSeverity
| extend ExtendedProperties_json= parse_json(ExtendedProperties)
| extend CurrentLocation = tostring(ExtendedProperties_json.["Current Location"])
| extend PreviousLocation = tostring(ExtendedProperties_json.["Previous Location"])
| extend CurrentIPAddress = tostring(ExtendedProperties_json.["Current IP Address"])
| extend PreviousIPAddress = tostring(ExtendedProperties_json.["Previous IP Address"])
| project AadTenantId, AadUserId, AccountTenantId, AccountObjectId, Alert_AtypicalTravels_Name, Alert_AtypicalTravels_Severity, Alert_AtypicalTravels_Time, CurrentIPAddress, PreviousIPAddress, CurrentLocation, PreviousLocation, UserAccount, UserName, CompromisedEntity
;
Alert_UnfamiliarSignInProps
| join kind=inner Alert_AtypicalTravels on UserAccount
| where abs(datetime_diff('minute', Alert_UnfamiliarSignInProps_Time, Alert_AtypicalTravels_Time)) <= TimeDeltaInMinutes
| extend TimeDelta = Alert_UnfamiliarSignInProps_Time - Alert_AtypicalTravels_Time
| project UserAccount, AadUserId, Alert_UnfamiliarSignInProps_Name, Alert_UnfamiliarSignInProps_Severity, Alert_UnfamiliarSignInProps_Time, Alert_AtypicalTravels_Name, Alert_AtypicalTravels_Severity, Alert_AtypicalTravels_Time, TimeDelta, CurrentLocation, PreviousLocation, CurrentIPAddress, PreviousIPAddress, UserName
| extend UserEmailName = split(UserAccount,'@')[0], UPNSuffix = split(UserAccount,'@')[1]
customDetails:
Alert1_Time: Alert_UnfamiliarSignInProps_Time
CurrentIPAddress: CurrentIPAddress
Alert1_Severity: Alert_UnfamiliarSignInProps_Severity
Alert2_Severity: Alert_AtypicalTravels_Severity
Alert2_Name: Alert_AtypicalTravels_Name
TimeDelta: TimeDelta
PreviousLocation: PreviousLocation
Alert2_Time: Alert_AtypicalTravels_Time
CurrentLocation: CurrentLocation
Alert1_Name: Alert_UnfamiliarSignInProps_Name
PreviousIPAddress: PreviousIPAddress
queryPeriod: 14d
name: Correlate Unfamiliar sign-in properties & atypical travel alerts
version: 1.0.8
description: |
'The combination of an Unfamiliar sign-in properties alert and an Atypical travel alert about the same user within a +10m or -10m window is considered a high severity incident.'
kind: Scheduled
status: Available
severity: High
entityMappings:
- fieldMappings:
- columnName: UserAccount
identifier: FullName
- columnName: UserEmailName
identifier: Name
- columnName: UPNSuffix
identifier: UPNSuffix
entityType: Account
- fieldMappings:
- columnName: AadUserId
identifier: AadUserId
entityType: Account
- fieldMappings:
- columnName: CurrentIPAddress
identifier: Address
entityType: IP
- fieldMappings:
- columnName: PreviousIPAddress
identifier: Address
entityType: IP
tactics:
- InitialAccess
requiredDataConnectors:
- dataTypes:
- SecurityAlert (IPC)
connectorId: AzureActiveDirectoryIdentityProtection
- dataTypes:
- IdentityInfo
connectorId: BehaviorAnalytics
id: a3df4a32-4805-4c6d-8699-f3c888af2f67
triggerOperator: gt
triggerThreshold: 0
relevantTechniques:
- T1078
queryFrequency: 1d
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID Protection/Analytic Rules/CorrelateIPC_Unfamiliar-Atypical.yaml
query: |
// We can use this configuration TimeDeltaInMinutes if you want to chnage the time window that we try to match the alerts
let TimeDeltaInMinutes = 10;
let Alert_UnfamiliarSignInProps =
SecurityAlert
| where TimeGenerated > ago(1d)
| where ProductName =~ "Azure Active Directory Identity Protection"
| where AlertName =~ "Unfamiliar sign-in properties"
| mv-expand Entity = todynamic(Entities)
| where Entity.Type =~ "account"
| extend AadTenantId = tostring(Entity.AadTenantId)
| extend AadUserId = tostring(Entity.AadUserId)
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserName = AccountDisplayName
| extend UserAccount = AccountUPN
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
| extend Alert_UnfamiliarSignInProps_Time = TimeGenerated
| extend Alert_UnfamiliarSignInProps_Name = AlertName
| extend Alert_UnfamiliarSignInProps_Severity = AlertSeverity
| project AadTenantId, AadUserId, AccountTenantId, AccountObjectId, Alert_UnfamiliarSignInProps_Name, Alert_UnfamiliarSignInProps_Severity, Alert_UnfamiliarSignInProps_Time, UserAccount, UserName
;
let Alert_AtypicalTravels =
SecurityAlert
| where TimeGenerated > ago(1d)
| where ProductName =~ "Azure Active Directory Identity Protection"
| where AlertName =~ "Atypical travel"
| mv-expand Entity = todynamic(Entities)
| where Entity.Type =~ "account"
| extend AadTenantId = tostring(Entity.AadTenantId)
| extend AadUserId = tostring(Entity.AadUserId)
| join kind=inner (
IdentityInfo
| distinct AccountTenantId, AccountObjectId, AccountUPN, AccountDisplayName
| extend UserName = AccountDisplayName
| extend UserAccount = AccountUPN
| where isnotempty(AccountDisplayName) and isnotempty(UserAccount)
| project AccountTenantId, AccountObjectId, UserAccount, UserName
)
on
$left.AadTenantId == $right.AccountTenantId,
$left.AadUserId == $right.AccountObjectId
| extend CompromisedEntity = iff(CompromisedEntity == "N/A" or isempty(CompromisedEntity), UserAccount, CompromisedEntity)
| extend Alert_AtypicalTravels_Time = TimeGenerated
| extend Alert_AtypicalTravels_Name = AlertName
| extend Alert_AtypicalTravels_Severity = AlertSeverity
| extend ExtendedProperties_json= parse_json(ExtendedProperties)
| extend CurrentLocation = tostring(ExtendedProperties_json.["Current Location"])
| extend PreviousLocation = tostring(ExtendedProperties_json.["Previous Location"])
| extend CurrentIPAddress = tostring(ExtendedProperties_json.["Current IP Address"])
| extend PreviousIPAddress = tostring(ExtendedProperties_json.["Previous IP Address"])
| project AadTenantId, AadUserId, AccountTenantId, AccountObjectId, Alert_AtypicalTravels_Name, Alert_AtypicalTravels_Severity, Alert_AtypicalTravels_Time, CurrentIPAddress, PreviousIPAddress, CurrentLocation, PreviousLocation, UserAccount, UserName, CompromisedEntity
;
Alert_UnfamiliarSignInProps
| join kind=inner Alert_AtypicalTravels on UserAccount
| where abs(datetime_diff('minute', Alert_UnfamiliarSignInProps_Time, Alert_AtypicalTravels_Time)) <= TimeDeltaInMinutes
| extend TimeDelta = Alert_UnfamiliarSignInProps_Time - Alert_AtypicalTravels_Time
| project UserAccount, AadUserId, Alert_UnfamiliarSignInProps_Name, Alert_UnfamiliarSignInProps_Severity, Alert_UnfamiliarSignInProps_Time, Alert_AtypicalTravels_Name, Alert_AtypicalTravels_Severity, Alert_AtypicalTravels_Time, TimeDelta, CurrentLocation, PreviousLocation, CurrentIPAddress, PreviousIPAddress, UserName
| extend UserEmailName = split(UserAccount,'@')[0], UPNSuffix = split(UserAccount,'@')[1]