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
CollectionT1005 Data from Local System

Event coverage

Rule body kusto

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 isfuzzy=true

Stage 2: source time_window=86400s

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

| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))

Stage 27: extend

| extend SubjectUserName = tostring(EventData.SubjectUserName)

Stage 28: extend

| extend SubjectDomainName = tostring(EventData.SubjectDomainName)

Stage 29: summarize

| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Stage 30: source

SecurityEvent

Stage 31: where

| where EventID == '4663'

Stage 32: where

| where ObjectType == 'Key'

Stage 33: where

| where ObjectName == aadHealthMonAgentRegKey

Stage 34: extend

| extend Process = tostring(split(ProcessName, '\\', -1)[-1])

Stage 35: where

| where Process !in (aadConnectHealthProcs)

Stage 36: summarize

| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Stage 37: source

WindowsEvent

Stage 38: where

| where EventID == '4663' and EventData has aadHealthMonAgentRegKey

Stage 39: extend

| extend ObjectType = tostring(EventData.ObjectType)

Stage 40: where

| where ObjectType == 'Key'

Stage 41: extend

| extend ObjectName = tostring(EventData.ObjectName)

Stage 42: where

| where ObjectName == aadHealthMonAgentRegKey

Stage 43: extend

| extend ProcessName = tostring(EventData.ProcessName)

Stage 44: extend

| extend Process = tostring(split(ProcessName, '\\')[-1])

Stage 45: where

| where Process !in (aadConnectHealthProcs)

Stage 46: extend

| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))

Stage 47: extend

| extend SubjectUserName = tostring(EventData.SubjectUserName)

Stage 48: extend

| extend SubjectDomainName = tostring(EventData.SubjectDomainName)

Stage 49: summarize

| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Stage 50: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 51: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Stage 52: extend

| extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])

Stage 53: project-away

| project-away DomainIndex

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

StageFieldKindExcluded values
15ProcessinMicrosoft.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
25ProcessinMicrosoft.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
35ProcessinMicrosoft.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
45ProcessinMicrosoft.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

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
EventDatamatch
  • \\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Microsoft Online\\Reporting\\MonitoringAgent
EventIDeq
  • 4656 transforms: cased corpus 19 (splunk 15, kusto 4)
  • 4663 transforms: cased corpus 34 (splunk 29, kusto 5)
ObjectNameeq
  • \\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Microsoft Online\\Reporting\\MonitoringAgent transforms: cased
ObjectTypeeq
  • Key transforms: cased corpus 8 (sigma 4, kusto 4)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

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