Combining Azure Identity Protection alerts with the join operator

By Gianni Castaldi

In the previous blog post, we have learned how the join operator works and how we can use it. In this blog post, we will walk through the process of creating a new detection with this operator.

When we look at the incident history at we see that at least 20% of all incidents are unfamiliar sign-in properties, reported by Azure Active Directory Identity Protection. This is not a problem for our 10 users, but what if you manage 100k users.

So that is why we want to correlate the unfamiliar sign-in properties alert, to a different alert and increase the severity of the alert. For this blog post, we will use the atypical travel alert.

This detection is built in 4 steps.

  1. Get all the alerts with the alert name unfamiliar sign-in properties
| where TimeGenerated > TimeFrame
| where AlertName == "Unfamiliar sign-in properties"
| extend UserPrincipalName = tostring(parse_json(ExtendedProperties).["User Account"])
| extend Alert1Time = TimeGenerated
| extend Alert1 = AlertName
| extend Alert1Severity = AlertSeverity

We have parsed the user account to UserPrincipalName so we can easily join it to the second alert. The Alert1Time will be used to match the time with the atypical travel alerts. The Alert1 and the Alert1Severity are there to provide information about the first alert.

  1. Get all the alerts with atypical travel
| where TimeGenerated > TimeFrame
| where AlertName == "Atypical travel"
| extend UserPrincipalName = tostring(parse_json(ExtendedProperties).["User Account"])
| extend Alert2Time = TimeGenerated
| extend Alert2 = AlertName
| extend Alert2Severity = AlertSeverity
| extend CurrentLocation = strcat(tostring(parse_json(tostring(parse_json(Entities)[1].Location)).CountryCode), "|", tostring(parse_json(tostring(parse_json(Entities)[1].Location)).State), "|", tostring(parse_json(tostring(parse_json(Entities)[1].Location)).City))
| extend PreviousLocation = strcat(tostring(parse_json(tostring(parse_json(Entities)[2].Location)).CountryCode), "|", tostring(parse_json(tostring(parse_json(Entities)[2].Location)).State), "|", tostring(parse_json(tostring(parse_json(Entities)[2].Location)).City))
| extend CurrentIPAddress = tostring(parse_json(Entities)[1].Address)
| extend PreviousIPAddress = tostring(parse_json(Entities)[2].Address)

We have again parsed the atypical travel alert the same way we have parsed the unfamiliar sign-in properties. But we have added the locations and IP addresses of the atypical travel to assess the alert.

  1. Join the tables with an inner join on the user principal name
    • where the atypical travel alert is created 10 minutes before or after the unfamiliar sign-in properties alert
| join kind=inner Alert2 on UserPrincipalName
| where (Alert1Time - Alert2Time) between (-10min..10min)

We have used the inner join because we want to see all combinations based on the UserPrincipalName. With the where operator we have filtered the results to only display the results where the atypical travel alert has occurred 10 minutes before or after the unfamiliar sign-in properties. We could have chosen for a broader range but that could also increase the false positives.

  1. Project the columns to have enough data to assess the alert
| project UserPrincipalName, Alert1, Alert1Time, Alert1Severity, Alert2, Alert2Time, Alert2Severity, CurrentLocation, PreviousLocation, CurrentIPAddress, PreviousIPAddress

In the last step, we chose to display more than just the time, IP address, and user principal name. By adding the location we can easily assess if the location is known.

To make this detection searchable and accessible we have made it available in this GitHub gist.

Thanks for reading and if you have any questions or ideas for a blog post let me know.

Alternative Text

By Gianni Castaldi

MVP | NinjaCat | Researching and Engineering Cyber Security @ KustoWorks

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.