Enriching Microsoft Sentinel tables with eligible Entra directory roles

Nicola
6 min readNov 17, 2023

--

Microsoft 365 Defender and Sentinel provide an IdentityInfo table that contains various information that is helpful for threat hunting and detections. One key piece are also the assigned Entra directory roles for a specific identity. Unfortunately only permanently assigned permissions are covered and in times of Entra Privileged Identity Management (PIM) we should have standing permissions only for non-privileged roles and break-glass accounts.

Within this blog post I want to share a few tips and tricks to answer the following questions with Sentinel and a little bit of scripting and KQL:

  • How can we enrich the IdentityInfo table to include eligible assigned directory roles?
  • Which synchronized user accounts have permanent or eligible directory roles assigned? (Spoiler: this should be avoided at all cost)
  • Were eligible directory role assignments not used within the last couple of days and can therefore be removed?

As a bonus I also prepared an analytics rule for mass unassignment of highly privileged Entra roles, as this tactic was used for example by the LAPSUS$ group.

Gathering PIM eligible Entra Directory Roles

As the IdentityInfo and other available built-in data sources do not include eligible role assignments we need a way to gather the existing role assignments. Fortunately, we can query the following Microsoft Graph endpoint to get the eligible permission assignments:

GET /roleManagement/directory/roleEligibilitySchedules?$expand=roleDefinition,principal

This will return the assigned role and the principal information. In case that the role was assigned to an Entra role-assignable group, the principal object holds the group id. To allow simplified enrichment without group membership lookups we can easily un-nest the assignments by getting the group members to have a flat list of users with their eligible role assignments.

For the data collection we have the following options:

  • Run this on demand as an interactive PowerShell script
  • Set it up as an Azure automation PowerShell runbook that will upload the CSV directly to a storage account
  • Build a logic app

PowerShell approach

You can find both options available on my ITDR GitHub Repository:

Azure Automation Runbook to dump the PIM Entra role assignments

Just make sure, that the managed identity of the Automation Account has the following permissions:

Once the CSV export of the runbook completed, we can create a shared access signature (SAS) to use the data within our queries:

CSV export of the automation runbook

Here an example of the exported CSV from my lab tenant:

Logic App approach

Another approach is to setup a logic app that will acquire and feed the watchlist directly. This also allows for initial population without a manual upload as Azure provides a dedicated API endpoint for this to bulk create or update the watchlist: Watchlists — Create Or Update — REST API.

The API endpoint uses the PUT HTTP verb but unfortunately the operation is not really idempotent as the watchlist items are not being overwritten, actually just being added. Nevertheless this provides a quick solution to propagate and use the data.

The managed identity of the logic app will require the following permissions:

  • Microsoft Sentinel Contributor (Azure IAM)
  • RoleManagement.Read.All (Microsoft Graph API)
Logic App to leverage the Sentinel watchlist API

You can find an export of my logic app here: Entra-ID-PIM-Eligible-Directory-Roles/la-pimroleassignments.json. Just make sure to update the managed identity references and variables to include your Sentinel workspace info.

Sentinel Integration

After we have a dump of all PIM eligible assignments let’s bring this data into Sentinel. We have two options:

  • Import the CSV as watchlist and access the data via _GetWatchlist() function
  • Embed the CSV via the externaldata function

With both options we have the CSV available as KQL-like table:

Data available in Sentinel via getwatchlist or externaldata function

The advantage with the externaldata operator is, that we have always the latest data available, in case you go for the Azure automation runbook and schedule it to run periodically, e.g. on a daily basis. Alternatively you could also automatically update the watchlist with a logic app or a similar mechanism. Here an example for the externaldata call:

let EligibleDirectoryRoles =  externaldata(PrincipalId: guid, PrincipalType: string, UserPrincipalName: string, RoleDefinitionId: guid, RoleDefinitionName: string, DirectoryScopeId: string, AssignmentInheritedFrom: guid, AssignmentType:string) [
h@'https://stsecopsn01.blob.core.windows.net/watchlists/eligibleRoleAssignments.csv?...'
] with (format=csv, ignoreFirstRecord=true);
The CSV can also be stored as watchlist.

IdentityInfo Enrichment

After having the PIM role assignments in Sentinel, we can start extending the IdentityInfo table:

Now we have an IdentityInfo table with the EligibleDirectoryRoles column for each particular user account. This shows us both the permanently assigned and eligible directory roles 😎:

We have now the enriched IdentityInfo table with the EligibleDirectoryRoles Column

Additional Queries

Last PIM activation per role assignment

With the individual PIM role assignments available, we can query the last activation timestamp based on the Entra audit logs. This benefits the assessment of stale role assignments that can be removed. Depending on the result, this could also be an indicator whether an admin always elevates to the more-privileged role instead of the individual service roles.

Finding the last activation for given PIM assignments

Synchronized user accounts with admin roles

Account separation is a very effective measure to limit the blast radius in case of on-premises compromise or account takeover. With the following query we can identify both permanent and eligible role assignments for synchronized user accounts:

Public IPs used for role activation

We can extend the above query to also parse the public IP addresses that were used to activate certain role assignments. This could be the foundation to implement a Conditional Access policy to limit the role activation via authentication context or additional detection rules:

Parsing the used public IP addresses for PIM role activations

Malicious removal of Directory Roles

LAPSUS$ used the Mitre ATT&CK technique ‘Account Access Removal’ (T1531) to prevent victim tenants from accessing their cloud infrastructure with administrative accounts. We can use a detection rule to identify bulk-removal of highly sensitive directory roles that can lead to lockout of a Microsoft Entra Tenant.

The detection rule gathers Entra directory role removals for Global Administrators and Privileged Role Administrators.

Bulk removal of highly privileged directory roles

Link to the full detection rule: ITDR/Detections/T1531.Entra.HighlyPrivilegedRoleRemoval.md

We can also setup this detection as a Sentinel Analytics rule that will generate alerts for mass-role-unsassignments:

Sentinel Analytics rule to detect mass role-unassignment.

Link to the analytics rule: T1531.Entra.HighlyPrivilegedRoleRemoval.yaml.

Conclusion

We already have the tools and the data available to correlate detections and hunting with PIM eligible role assignments so let’s use them to defend against attacks on admin accounts that don’t have standing permissions in place. For the future I’d hope that a column such as the EligibleRoleAssignments would be baked in into the Identity UEBA table to make this information accessible out of the box.

// Ciao 👋

--

--

Nicola

Interested in endpoint management, security, identity and automation. #Intune #AzureAD #Defender #PowerShell #Azure