Microsoft Sentinel custom analytics rules are amazingly simple yet powerful way to create security incidents based or events specific to your environment.
Microsoft provides good documentation and steps to Create custom analytics rules to detect threats
One of the use cases we have encountered is ability to monitor that changes and access to Azure resources are performed by expected groups of users.
Azure provides capabilities to log and monitor access easily (for example we can use AzureActivity and StorageBlobLogs tables in Azure Log Analytics). But cross-checking access against specific groups in Azure Active Directory requires additional steps as information about membership of Azure AD groups is not directly available in Log Analytics.
There are multiple ways to achieve this but for us the easiest to implement and maintain was to leverage Kusto "externaldata" operator.
To leverage "externaldata" for Azure AD group membership we created a SQL table with object IDs and names of groups that are authorized to access resources to be monitored. Then we created Azure function that on daily basis reads membership of those groups and logs it into a json file in Azure Storage account.
Once this is done validating access to Azure resources against Azure AD groups becomes a breeze and can be done in a single Kusto join query.
use "externaldata" operator to access blob
Reference blob that contains Azure AD group membership info
Provide Shared Access Signature (SAS) to ensure this information remains private and secure
Limit the output to the selected group of authorized users
Read Azure Activity Logs in Log Analytics workspace (assume you collecting all your Azure Changes in Log Analytics of course)
Specify timeframe to used to read access logs (suggest keep this low for performance reasons)
"anti-join" returns only users that do not belong to the expected group
join based on user principal name (of course can optimize this query to make more efficient but for illustration purposes left as-is)
The complete query is below. Just replace %your-storageaccount%, %your-SAS%, %your-group-name%, and %your-subscriptionid% according to your environment values.
let GroupMembers = externaldata(GroupObjectID:string,GroupName:string,UserPrincipalName: string)
[
h@'https://%your-storageaccount%.blob.core.windows.net/funcapp-output/ADGroupMembers.json?[%your-SAS%]'
]
with(format='multijson', ingestionMapping='[{"Column":"GroupObjectID","Properties":{"Path":"$.GroupObjectID"}},{"Column":"GroupName","Properties":{"Path":"$.GroupName"}},{"Column":"UserPrincipalName","Properties":{"Path":"$.UserPrincipalName"}}]') | where GroupName == '%your-group-name%';
AzureActivity | where SubscriptionId == '%your-subscriptionid%' and Caller contains '@' and TimeGenerated > ago(10d) | extend CallerLower = tolower(Caller) |
join kind=leftanti(GroupMembers ) on $left.CallerLower == $right.UserPrincipalName
Hope you find this approach useful.
Special Thanks to Tian Zheng for working together on this scenario!
Comments