Detection rules › Kusto

Microsoft Entra ID Health Monitoring Agent Registry Keys Access

Severity
medium
Time window
1d
Group by
Account, Computer, EventID, ObjectName, ObjectType, Process, ProcessName, SubjectDomainName, SubjectUserName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This detection uses Windows security events to detect suspicious access attempts to the registry key of Microsoft Entra ID Health monitoring agent. This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object HKLM\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_monitoring_agent.yml

MITRE ATT&CK coverage

TacticTechniques
Collection

Telemetry coverage

Rule body

id: f819c592-c5f9-4d5c-a79f-1e6819863533
name: Microsoft Entra ID Health Monitoring Agent Registry Keys Access
description: |
  'This detection uses Windows security events to detect suspicious access attempts to the registry key of Microsoft Entra ID Health monitoring agent.
  This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object HKLM\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent.
  You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_monitoring_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\\Microsoft Online\\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])
  | project-away DomainIndex
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.6
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\\Microsoft Online\\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

Stage 47: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDatamatch
  • \REGISTRY\MACHINE\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent transforms: term
field:"EventData" kind:match value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent"
EventIDeq
  • 4656 corpus 19 (splunk 15, kusto 4)
  • 4663 corpus 36 (splunk 31, kusto 5)
field:"EventID" kind:eq
ObjectNameeq
  • \REGISTRY\MACHINE\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent
field:"ObjectName" kind:eq value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent"
ObjectTypeeq
  • Key corpus 4 (kusto 4)
field:"ObjectType" kind:eq value:"Key"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ProcessinMicrosoft.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.exeexcludes:Process

Output fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
Computersummarize
EndTimesummarize
EventIDsummarize
ObjectNamesummarize
ObjectTypesummarize
Processsummarize
ProcessNamesummarize
StartTimesummarize
SubjectDomainNamesummarize
SubjectUserNamesummarize
HostNameextend
HostNameDomainextend
NTDomainextend
Nameextend