Unusual audit activity initiated by a single user may indicate an attempt to manipulate access controls or escalate privileges. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential insider threats or unauthorized changes to user/group permissions.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let auditLookback = starttime - 14d;
let propertyIgnoreList = dynamic(["TargetId.UserType", "StsRefreshTokensValidFrom", "LastDirSyncTime", "DeviceOSVersion", "CloudDeviceOSVersion", "DeviceObjectVersion"]);
let AuditTrail = AuditLogs
| where TimeGenerated >= auditLookback and TimeGenerated < starttime
| where isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend InitiatedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatedByIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend ModProps = TargetResources.[0].modifiedProperties
| extend TargetUserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
| extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName))
| mv-expand ModProps
| extend PropertyName = tostring(ModProps.displayName), newValue = tostring(parse_json(tostring(ModProps.newValue))[0])
| where PropertyName !in~ (propertyIgnoreList) and (PropertyName !~ "Action Client Name" and newValue !~ "DirectorySync") and (PropertyName !~ "Included Updated Properties" and newValue !~ "LastDirSyncTime")
| summarize count() by OperationName, InitiatedByUser, InitiatedByIPAddress, TargetUserPrincipalName, PropertyName, TargetResourceName;
let AccountMods = AuditLogs
| where TimeGenerated >= starttime
| where isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend InitiatedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatedByIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend ModProps = TargetResources.[0].modifiedProperties
| extend TargetUserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
| extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName))
| mv-expand ModProps
| extend PropertyName = tostring(ModProps.displayName), newValue = tostring(parse_json(tostring(ModProps.newValue))[0])
| where PropertyName !in~ (propertyIgnoreList) and (PropertyName !~ "Action Client Name" and newValue !~ "DirectorySync") and (PropertyName !~ "Included Updated Properties" and newValue !~ "LastDirSyncTime")
| extend ModifiedProps = pack("PropertyName",PropertyName,"newValue",newValue, "Id", Id, "CorrelationId", CorrelationId)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Activity = make_bag(ModifiedProps) by Type, InitiatedByUser, InitiatedByIPAddress, TargetUserPrincipalName, Category, OperationName, PropertyName, TargetResourceName;
let RareAudits = AccountMods | join kind= leftanti (
AuditTrail
) on OperationName, InitiatedByUser, InitiatedByIPAddress;//, TargetUserPrincipalName, PropertyName; //uncomment if you want to see Rare Property changes to a given TargetUserPrincipalName.
RareAudits
| summarize StartTime = min(StartTimeUtc), EndTime = max(EndTimeUtc), make_set(Activity), make_set(PropertyName) by Type, InitiatedByUser, InitiatedByIPAddress, OperationName, TargetUserPrincipalName, TargetResourceName
| order by InitiatedByUser asc, StartTime asc
| extend timestamp = StartTime, AccountCustomEntity = InitiatedByUser, HostCustomEntity = iff(set_PropertyName has_any ('DeviceOSType', 'CloudDeviceOSType'), TargetResourceName, ''), IPCustomEntity = InitiatedByIPAddress
id: ea107ccc-2b80-410e-96e1-be6607ce293b
name: Rare Audit activity initiated by User
description: |
'Compares current day to last 14 days of audits to identify new audit activities. Useful for tracking malicious activity related to user/group additions/removals by specific users.'
description_detailed: |
'Compares the current day to the last 14 days of audits to identify new audit activities by
OperationName, InitiatedByUser, UserPrincipalName, PropertyName, newValue
This can be useful when attempting to track down malicious activity related to additions of
new users, additions to groups, removal from groups by specific users.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
- LateralMovement
relevantTechniques:
- T1136
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let auditLookback = starttime - 14d;
let propertyIgnoreList = dynamic(["TargetId.UserType", "StsRefreshTokensValidFrom", "LastDirSyncTime", "DeviceOSVersion", "CloudDeviceOSVersion", "DeviceObjectVersion"]);
let AuditTrail = AuditLogs
| where TimeGenerated >= auditLookback and TimeGenerated < starttime
| where isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend InitiatedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatedByIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend ModProps = TargetResources.[0].modifiedProperties
| extend TargetUserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
| extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName))
| mv-expand ModProps
| extend PropertyName = tostring(ModProps.displayName), newValue = tostring(parse_json(tostring(ModProps.newValue))[0])
| where PropertyName !in~ (propertyIgnoreList) and (PropertyName !~ "Action Client Name" and newValue !~ "DirectorySync") and (PropertyName !~ "Included Updated Properties" and newValue !~ "LastDirSyncTime")
| summarize count() by OperationName, InitiatedByUser, InitiatedByIPAddress, TargetUserPrincipalName, PropertyName, TargetResourceName;
let AccountMods = AuditLogs
| where TimeGenerated >= starttime
| where isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))
| extend InitiatedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatedByIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend ModProps = TargetResources.[0].modifiedProperties
| extend TargetUserPrincipalName = tolower(tostring(TargetResources.[0].userPrincipalName))
| extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName))
| mv-expand ModProps
| extend PropertyName = tostring(ModProps.displayName), newValue = tostring(parse_json(tostring(ModProps.newValue))[0])
| where PropertyName !i
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled System Audit Job Running
Description: A legitimate scheduled job (e.g., Auditpol or Audit Collection Services in Windows) is executed to collect audit logs.
Filter/Exclusion: EventID = 10000 (for Auditpol) or EventSource = "Microsoft-Windows-Security-Auditing" with EventID = 4624 or EventID = 4625 (for authentication events).
Scenario: User-Initiated Audit Policy Change
Description: An admin manually changes audit policies using tools like Auditpol or the Group Policy Management Console (GPMC).
Filter/Exclusion: EventID = 10000 (Auditpol) or EventID = 4625 (audit policy change via GPMC).
Scenario: User Adds Themselves to an Audit Log Group
Description: A user adds themselves to a group that has audit logging enabled, such as the Audit Log Users group.
Filter/Exclusion: EventID = 4726 (User Account Management) with TargetUserName = [User] or TargetSID = [UserSID].
Scenario: Automated Compliance Tool Initiates Audit
Description: A third-party compliance tool (e.g., Microsoft Intune, Azure Security Center, or Qualys) initiates an audit scan as part of routine compliance checks.
Filter/Exclusion: EventSource = "Intune" or EventSource = "AzureSecurityCenter" or EventSource = "Qualys".
Scenario: User Receives Audit Notification from Email or Ticketing System
Description: A user receives an audit-related email or ticket (e.g., from ServiceNow or J