Enriching Microsoft Sentinel tables with eligible Entra directory roles
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:
- Interactive: Get-AssignableRoles.ps1
- Azure Automation runbook: Get-AssignableRoles-Automation.ps1
Just make sure, that the managed identity of the Automation Account has the following permissions:
- Storage Blob Data Contributor (Azure IAM)
- RoleManagement.Read.All (Microsoft Graph API)
Once the CSV export of the runbook completed, we can create a shared access signature (SAS) to use the data within our queries:
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)
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:
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);
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 😎:
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.
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:
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.
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:
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 👋