For those that use Sentinel, hopefully you have turned on the User and Entity Behaviour Analytics, the cost is fairly negligible and it’s what drives the entity and investigation experiences in Sentinel. There are plenty of articles and blogs around to cover how to use those. I wanted to give you some really great examples of leveraging the same information to make your investigation and rules even better.
When you turn on UEBA you end up with four new tables
- BehaviorAnalytics – this tracks things like logons or group changes, but goes beyond that and measures if the event is uncommon
- UserAccessAnalytics – tracks users access, such as group membership but also maintains information such as when the access was first granted
- PeerAccessAnalytics – maintains a list of a users closest peers which helps to evaluate potential blast radius
- IdentityUserInfo – maintains a table of identity info from both on premise and cloud for users
We have access those like any other tables even when not using the entity or investigation pages. So let’s have a look at a few examples of using that data to make meaningful queries. The IdentityInfo table is a combination of Azure AD and on-premise AD data and it is a godsend – especially for those of us who still have a large on premise footprint. Previously you had to ingest a lot of this data yourself. Have a read of the Tech Community post here which has the details of this table. We essentially turn our identity data into log data, which is great for threat hunting. You just need to make sure you write your queries to account for multiple entries for users, such as using the take operator, or the arg_max operator.
Have a system that likes to respond using SIDs for users alerts instead of usernames? Here we look for lockout events, grab the SID of the account and then join to the IdentityInfo table where we get information that is actually useful to us. Remember that the IdentityInfo is a table and will have multiple entries for users, so just retrieve the latest record
let alert=
SecurityEvent
| where EventID == "4740"
| extend AccountSID = TargetSid
| project AccountSID, Activity;
IdentityInfo
| join kind=inner alert on AccountSID
| sort by TimeGenerated desc
| take 1
| project AccountName, Activity, AccountSID, AccountDisplayName, JobTitle, Phone, IsAccountEnabled, AccountUPN

Do you grant access to an admin server for your IT staff and want to audit to make sure its being used? This query will find the enabled members of the group “ADMINSERVER01 – RDP Access” then query for successful RDP logons to it. We use the rightanti join in Kusto, and the output will be users who have access, but haven’t connected in 30 days.
let users=
IdentityInfo
| where TimeGenerated > ago (7d)
| where GroupMembership has "ADMINSERVER01 - RDP Access"
| extend OnPremAccount = AccountName
| where IsAccountEnabled == true
| distinct OnPremAccount, AccountUPN, EmployeeId, IsAccountEnabled;
SecurityEvent
| where TimeGenerated > ago (30d)
| where EventID == 4624
| where LogonType == 10
| where Computer has "ADMINSERVER01"
| sort by TimeGenerated desc
| extend OnPremAccount = trim_start(@"DOMAIN\\", Account)
| summarize arg_max (TimeGenerated, *) by OnPremAccount
| join kind=rightanti users on OnPremAccount
| project OnPremAccount, AccountUPN, IsAccountEnabled
Have an application that you use Azure AD for SSO, but access control is granted from on premise AD groups? You can do a similar join to SigninLogs data.
let users=
IdentityInfo
| where TimeGenerated > ago (7d)
| where GroupMembership has "Business App Access"
| extend UserPrincipalName = AccountUPN
| distinct UserPrincipalName, EmployeeId, IsAccountEnabled;
SigninLogs
| where TimeGenerated > ago (30d)
| where AppDisplayName contains "Business App"
| where ResultType == 0
| sort by TimeGenerated desc
| summarize arg_max(TimeGenerated, AppDisplayName) by UserPrincipalName
| join kind=rightanti users on UserPrincipalName
| project UserPrincipalName, EmployeeId, IsAccountEnabled
Again this will show you who has access but hasn’t authenticated via Azure AD in 30 days. Access reviews in Azure AD can help you with this too, but it’s a P2 feature you may not have, and it won’t be able to change on premise AD group membership.
You could query the IdentityInfo table for users with certain privileged Azure AD roles and correlate with Cloud App Security alerts to prioritize them higher.
let PrivilgedRoles = dynamic(["Global Administrator","Security Administrator","Teams Administrator", "Security Administrator"]);
let PrivilegedIdentities =
IdentityInfo
| summarize arg_max(TimeGenerated, *) by AccountObjectId
| mv-expand AssignedRoles
| where AssignedRoles in~ (PrivilgedRoles)
| summarize AssignedRoles=make_set(AssignedRoles) by AccountObjectId, AccountSID, AccountUPN, AccountDisplayName, JobTitle, Department;
SecurityAlert
| where TimeGenerated > ago (7d)
| where ProviderName has "MCAS"
| project CompromisedEntity, AlertName, AlertSeverity
| join kind=inner PrivilegedIdentities on $left.CompromisedEntity == $right.AccountUPN
| project TimeGenerated, AccountDisplayName, AccountObjectId, AccountSID, AccountUPN, AlertSeverity, AlertName, AssignedRoles
And finally using the same logic to find users with privileged roles and detecting any Azure AD Conditional Access failures for them
let PrivilgedRoles = dynamic(["Global Administrator","Security Administrator","Teams Administrator", "Exchange Administrator"]);
let PrivilegedIdentities =
IdentityInfo
| summarize arg_max(TimeGenerated, *) by AccountObjectId
| mv-expand AssignedRoles
| where AssignedRoles in~ (PrivilgedRoles)
| summarize AssignedRoles=make_set(AssignedRoles) by AccountObjectId, AccountSID, AccountUPN, AccountDisplayName, JobTitle, Department;
SigninLogs
| where TimeGenerated > ago (30d)
| where ResultType == 53003
| join kind=inner PrivilegedIdentities on $left.UserPrincipalName == $right.AccountUPN
| project TimeGenerated, AccountDisplayName, AccountObjectId, AccountUPN, AppDisplayName, IPAddress
Remember that once you join your IdentityInfo table to whichever other data sources, you can include fields from both in your queries – so on premise SID’s or ObjectID’s as well as items from your SigninLogs or SecurityAlert tables like alert names, or conditional access failures.
IdentityInfo like in the queries, or identityUserInfo as in the list of tables?
LikeLike
IdentityInfo should be the name of the table (and used in the queries) if you have UEBA enabled
LikeLike
2 Pingbacks