Detection rules › Kusto
Microsoft Entra ID Health Service Agents Registry Keys Access
This detection uses Windows security events to detect suspicious access attempts to the registry key values and sub-keys of Microsoft Entra ID Health service agents (e.g AD FS). Information from AD Health service agents can be used to potentially abuse some of the features provided by those services in the cloud (e.g. Federation). This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object: HKLM:\SOFTWARE\Microsoft\ADHealthAgent. Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Collection |
Telemetry coverage
Rule body
id: 06bbf969-fcbe-43fa-bac2-b2fa131d113a
name: Microsoft Entra ID Health Service Agents Registry Keys Access
description: |
'This detection uses Windows security events to detect suspicious access attempts to the registry key values and sub-keys of Microsoft Entra ID Health service agents (e.g AD FS).
Information from AD Health service agents can be used to potentially abuse some of the features provided by those services in the cloud (e.g. Federation).
This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object: HKLM:\SOFTWARE\Microsoft\ADHealthAgent.
Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml
'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1005
tags:
- SimuLand
query: |
// ADHealth Monitoring Agent Registry Key
let aadHealthMonAgentRegKey = "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\MicrosoftOnline\\Reporting\\MonitoringAgent";
// Filter out known processes
let aadConnectHealthProcs = dynamic ([
'Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe',
'Microsoft.Identity.Health.Adfs.InsightsService.exe',
'Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe',
'Microsoft.Identity.Health.Adfs.PshSurrogate.exe',
'Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe',
'Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe',
'Microsoft.Identity.AadConnect.Health.AadSync.Host.exe',
'Microsoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe',
'miiserver.exe'
]);
(union isfuzzy=true
(
SecurityEvent
| where EventID == '4656'
| where EventData has aadHealthMonAgentRegKey
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| extend ObjectName = column_ifexists("ObjectName", ""),
ObjectType = column_ifexists("ObjectType", "")
| where ObjectType == 'Key'
| where ObjectName == aadHealthMonAgentRegKey
| extend SubjectUserName = column_ifexists("SubjectUserName", ""),
SubjectDomainName = column_ifexists("SubjectDomainName", ""),
ProcessName = column_ifexists("ProcessName", "")
| extend Process = split(ProcessName, '\\', -1)[-1],
Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| where Process !in (aadConnectHealthProcs)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
),
( WindowsEvent
| where EventID == '4656' and EventData has aadHealthMonAgentRegKey
| extend ObjectType = tostring(EventData.ObjectType)
| where ObjectType == 'Key'
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName == aadHealthMonAgentRegKey
| extend ProcessName = tostring(EventData.ProcessName)
| extend Process = tostring(split(ProcessName, '\\')[-1])
| where Process !in (aadConnectHealthProcs)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
),
(
SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where ObjectName == aadHealthMonAgentRegKey
| extend Process = tostring(split(ProcessName, '\\', -1)[-1])
| where Process !in (aadConnectHealthProcs)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
),
( WindowsEvent
| where EventID == '4663' and EventData has aadHealthMonAgentRegKey
| extend ObjectType = tostring(EventData.ObjectType)
| where ObjectType == 'Key'
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName == aadHealthMonAgentRegKey
| extend ProcessName = tostring(EventData.ProcessName)
| extend Process = tostring(split(ProcessName, '\\')[-1])
| where Process !in (aadConnectHealthProcs)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
)
)
// You can filter out potential machine accounts
//| where AccountType != 'Machine'
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: NTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.1.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Stage 0: let
let aadHealthMonAgentRegKey = "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\MicrosoftOnline\\Reporting\\MonitoringAgent";
let aadConnectHealthProcs = dynamic ([
'Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe',
'Microsoft.Identity.Health.Adfs.InsightsService.exe',
'Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe',
'Microsoft.Identity.Health.Adfs.PshSurrogate.exe',
'Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe',
'Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe',
'Microsoft.Identity.AadConnect.Health.AadSync.Host.exe',
'Microsoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe',
'miiserver.exe'
]);
Stage 1: union
union of 4 branches
Stage 2: source
SecurityEvent
Stage 3: where
| where EventID == '4656'
Stage 4: where
| where EventData has aadHealthMonAgentRegKey
Stage 5: extend
| extend EventData = parse_xml(EventData).EventData.Data
Stage 6: mv-expand
| mv-expand bagexpansion=array EventData
Stage 7: evaluate
| evaluate bag_unpack(EventData)
Stage 8: extend
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
Stage 9: evaluate
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
Stage 10: extend
| extend ObjectName = column_ifexists("ObjectName", ""),
ObjectType = column_ifexists("ObjectType", "")
Stage 11: where
| where ObjectType == 'Key'
Stage 12: where
| where ObjectName == aadHealthMonAgentRegKey
Stage 13: extend
| extend SubjectUserName = column_ifexists("SubjectUserName", ""),
SubjectDomainName = column_ifexists("SubjectDomainName", ""),
ProcessName = column_ifexists("ProcessName", "")
Stage 14: extend
| extend Process = split(ProcessName, '\\', -1)[-1],
Account = strcat(SubjectDomainName, "\\", SubjectUserName)
Stage 15: where
| where Process !in (aadConnectHealthProcs)
Stage 16: summarize
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
Stage 17: source
WindowsEvent
Stage 18: where
| where EventID == '4656' and EventData has aadHealthMonAgentRegKey
Stage 19: extend
| extend ObjectType = tostring(EventData.ObjectType)
Stage 20: where
| where ObjectType == 'Key'
Stage 21: extend
| extend ObjectName = tostring(EventData.ObjectName)
Stage 22: where
| where ObjectName == aadHealthMonAgentRegKey
Stage 23: extend
| extend ProcessName = tostring(EventData.ProcessName)
Stage 24: extend
| extend Process = tostring(split(ProcessName, '\\')[-1])
Stage 25: where
| where Process !in (aadConnectHealthProcs)
Stage 26: extend (3 consecutive steps)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
Stage 27: summarize
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
Stage 28: source
SecurityEvent
Stage 29: where
| where EventID == '4663'
Stage 30: where
| where ObjectType == 'Key'
Stage 31: where
| where ObjectName == aadHealthMonAgentRegKey
Stage 32: extend
| extend Process = tostring(split(ProcessName, '\\', -1)[-1])
Stage 33: where
| where Process !in (aadConnectHealthProcs)
Stage 34: summarize
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
Stage 35: source
WindowsEvent
Stage 36: where
| where EventID == '4663' and EventData has aadHealthMonAgentRegKey
Stage 37: extend
| extend ObjectType = tostring(EventData.ObjectType)
Stage 38: where
| where ObjectType == 'Key'
Stage 39: extend
| extend ObjectName = tostring(EventData.ObjectName)
Stage 40: where
| where ObjectName == aadHealthMonAgentRegKey
Stage 41: extend
| extend ProcessName = tostring(EventData.ProcessName)
Stage 42: extend
| extend Process = tostring(split(ProcessName, '\\')[-1])
Stage 43: where
| where Process !in (aadConnectHealthProcs)
Stage 44: extend (3 consecutive steps)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
Stage 45: summarize
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
Stage 46: extend (3 consecutive steps)
extend DomainIndex, HostName, HostNameDomain, NTDomain, Name
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent" |
EventID | eq |
| field:"EventID" kind:eq |
ObjectName | eq |
| field:"ObjectName" kind:eq value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent" |
ObjectType | eq |
| field:"ObjectType" kind:eq value:"Key" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Process | in | Microsoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe, Microsoft.Identity.AadConnect.Health.AadSync.Host.exe, Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe, Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe, Microsoft.Identity.Health.Adfs.InsightsService.exe, Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe, Microsoft.Identity.Health.Adfs.PshSurrogate.exe, Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe, miiserver.exe | excludes:Process |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
Computer | summarize |
EndTime | summarize |
EventID | summarize |
ObjectName | summarize |
ObjectType | summarize |
Process | summarize |
ProcessName | summarize |
StartTime | summarize |
SubjectDomainName | summarize |
SubjectUserName | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
NTDomain | extend |
Name | extend |