Dastardly Detection of DirSync Deleted Groups
A corner case to investigate some of Microsoft Sentinels capabilities
The following is a corner case but an interesting issue that may come up from time to time in larger enterprises operation with Azure AD Connect and directory syncing.
It is Friday night and you receive a frantic call that a majority of employees in your Machinery Business Unit are unable to access Azure VPN to sign off timesheets in the retail side of the business or maybe some other AAD Enterprise Application is no longer work for a majority of employees.
In the course of investigation you find that a AAD Security Group “Machinery & Equipment“ is no longer a member of the Enterprise Application ‘“Azure VPN“. Further digging you find that the Local AD Security Group “Machinery & Equipment“ is in a OU, and further troubleshooting you find the OU is not one that directory syncs. Emailing and on Teams you find from your colleagues that a person within the Identity Team was cleaning up Users and Groups on Friday afternoon and had moved the Machinery & Equipment group into a different OU.
Problem Statement: My team wants to be alerted to membership changes especially removed Security Groups in a Enterprise Application specifically caused by a directory sync removing the AAD Security Group.
A quick scanning of the AAD AuditLogs shows that the AAD Security Group was deleted but does not have a entry for the Enterprise Application losing the membership of the Security group as a cause of the Deleted Group
So how can we alert when a Enterprise Application loses a Security group in it’s membership due to the cause above when the AAD Audit logs will not create an entry for this specific activity taking place ?
To craft a solution using Microsoft Sentinel you may need a way to either store the state of AAD Enterprise Applications and track change, or in this case you can use a watchlist to track the AAD Security Groups that are members of your Enterprise Applications.
In order to generate the list of AAD Security groups as members of a Enterprise Application, we can rely on a handy and easy to use PowerShell script to generate a csv:
#date of ran log
$FileLogDate = Get-Date -format "MMddyyyyhhmm"
# saving aad entprise application group memberships
$FileName = "aadentapppermissions_" + $FileLogDate + ".csv"
$OutputPath = "c:\temp\" + $FileName
$OutputFile = "c:\temp\aadentapppermissions_" + $FileLogDate + ".csv"
# Optional used to output the file into a storage account if desired externaldata()
<#
$StorageName = "aadstorenrs"
$ContainerName = "entappsreports"
#>
# Collect All Azure Enterpise Applications
$ServicePrincipalList = Get-AzureADServicePrincipal -All $true
# For each Enterpise Application check it's membership, filter on AAD Sec Groups in Membership and write in a exportable report .csv
foreach($servicePrincipal in $ServicePrincipalList) {
# Get a list of the memberships in the Enterpise Application, they can typically comprise of Users and Groups.
$Assignments = Get-AzureADServiceAppRoleAssignment -ObjectId $ServicePrincipal.objectId | Select-Object ResourceDisplayName, ResourceId, PrincipalDisplayName, PrincipalType
# For each membership check and filter on only Groups
foreach ($assignment in $Assignments){
# Filter for the AAD Security Group, could be removed to collect both Users and groups if ou want to monitor both in watchlist.
If ($Assignment.PrincipalType -eq "Group" ) {
# Export the Group and information of Enterprise Application in a .csv
$Assignment | Export-Csv -Path $Outputfile -NoTypeInformation -Append
}
}
}
# Optional Storage Context to send the .csv to a Storage Account Blob
<#$Context = New-AzStorageContext -StorageAccountName $StorageName -StorageAccountKey ""
#format .csv to be uploaded as a blob in storage account
$Blob = @{
File = $Outputfile
Container = $ContainerName
Blob = $FileName
Context = $Context
StandardBlobTier = 'Hot'
}
# Upload the .csv into storage account blob
Set-AzStorageBlobContent @Blob
#>
The outputted file will need some additional cleanup by doing a Find and Replace of the quotes “ to blank. This will allow Microsoft Sentinel to import the csv as a whitelist.
A brief detour because I love security automation, anything to free up engineers. An Azure Function or Logic App could be created with a timer driven approach to collect up to date AAD Security group memberships in Enterprise Applications and update the Microsoft Sentinel accordingly. I am going to use a phrase “Watcher” or “Oracle“. For those familiar with block chain the oracle lives outside the blockchain but is used to get pricing or other 3rd party information for a smart contract. In this case a ‘Watcher’ or ‘Oracle’ can be written to collect and update watchlists within Microsoft Sentinel. This concept is not new and you can visit one that collects the Clouds Azure, AWS, and GCPs Public IP Address Ranges into watchlists. Other concepts could be created as well and in backlog of someday I want to create a few more around TOR Exit browsers and your inside private ip addresses ranges in Azure, and then AWS and GCP.
Though outside of this blog today to walk through creating a watcher/oracle you can use the Azure Portal to create the Watchlist, In Microsoft Sentinel there is a dedicated blade for Watchlists, form here we can upload the modified CSV to generate the Watchlist for AAD Security groups that are members of AAD Enterprise Application.
With the watchlist in play now you can pivot to a Analytics Blade to create a KQL based alert rule called “Scheduled query rule“
As we create this detection we will use some cool features here to join the watchlist table of AAD group memberships of Enterprise Applications, custom details to capture the Enterprise Application impacted and a custom alert to help provide better context when reading the Incident.
While creating the alert we will use KQL
let watchlist = (_GetWatchlist('AADEntAppsMembership')
| project PrincipalDisplayName, SearchKey);
AuditLogs
| where OperationName == "Delete group"
| extend GroupName = tostring(TargetResources[0].displayName)
| extend userPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| where GroupName in (watchlist)
| lookup kind=leftouter _GetWatchlist('AADEntAppsMembership')
on $left.GroupName == $right.PrincipalDisplayName
The first blue area utilizes the watchlist alias in a let statement to help with searching within the watchlist as a filter later in line 7 yellow area.
The red area is the core basics of the rule we want to filter on delete groups in AAD AuditLogs, we want to extend out some additional columns for later use in entities and custom details for the AAD Security Group deleted and the account UPN responsible for deleting the group, in our scenario the AADSync account.
In yellow is where we take the results from Red and filter them further against the watch list so that we eliminate and AAD Security group deletes that are not part of a Enterprise Application membership.
The final green area does a simple join between the AAD Security group deleted, so we can bring in the watchlist table and the Enterprise Application(s) affected by the delete. We will use this later in the custom details and custom alert messaging to help clue an analyst or someone responding to the incident to connect the dots to the outage or the impact.
Next we you can map the entities involved in case you want to do automation off them or have them readily available in the security alert/incident for quick access to see impact.
You can further take advantage of Custom Details and Alert details by expanding the carrots below
We can generate some additional pairs of keys:values that might not be definable in the entities section in our case we want to highlight three
the AAD Enterprise Application involved in the membership deletion
the AAD Security group deleted and removed form the Enterprise Application
the Account Deleting the AAAD Security Group most likely the DirSync account involved but possible a admin.
Finally you can influence how the incident title and description will look by dynamically placing in the values of the three custom details we outlined above.
By using the {{ colum name }} we can bring that value in like
Title: Watchlist AAD Security Group: {{GroupName}} Deleted by: {{userPrincipalName}}
Description: AAD Security Group Deleted: {{GroupName}} by {{userPrincipalName}} - Enterprise App: {{SearchKey}} Membership Impacted
Again these custom alerts can clue the analyst or responder to the areas they need to check and help fulfil the problem statement you designed with n mind.
because impact to a Enterprise Application could be important we will schedule this to run and scan 5 mins back, the lowest you can use without going to a new type of rule called Near Real Time (NRT)
We will accept the defaults from here on out, be sure to check out this Security Automation playbook ‘Send-email-with-formatted-incident-report‘ which can be used to email the incident to the proper identity team as well.
You can now trigger this alert/incident by adding a synced AAD Security group as a member to a Enterprise Application, and then in local AD moving the security group to another OU that does not sync causing a delete upon next sync or a quck cmdlet to manual sync on the server running AAD Connect software.
Within a few minutes a new Incident appears with the relevant information to act and investigate upon
The following build out is for a corner case but you may have learned some new tricks or tips using Microsoft Sentinel.
Using the tooling in creative ways can reduce reliance and dependence on 3rd party products and lower your costs in the year.