Detection rules › Kusto

Microsoft Entra ID Health Service Agents 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 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

TacticTechniques
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.

FieldKindValuesSearch
EventDatamatch
  • \REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent transforms: term
field:"EventData" kind:match value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\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\MicrosoftOnline\Reporting\MonitoringAgent
field:"ObjectName" kind:eq value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\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
DomainIndexextend
HostNameextend
HostNameDomainextend
NTDomainextend
Nameextend