Detection rules › Kusto

Microsoft Entra ID Hybrid Health AD FS Service Delete

Status
available
Severity
medium
Time window
1d
Source
github.com/Azure/Azure-Sentinel

This detection uses AzureActivity logs (Administrative category) to identify the deletion of an Microsoft Entra ID Hybrid Health AD FS service instance in a tenant. A threat actor can create a new AD Health ADFS service and create a fake server to spoof AD FS signing logs. The health AD FS service can then be deleted after it is no longer needed via HTTP requests to Azure. More information is available in this blog https://o365blog.com/post/hybridhealthagent/

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: 86a036b2-3686-42eb-b417-909fc0867771
name: Microsoft Entra ID Hybrid Health AD FS Service Delete
description: |
  'This detection uses AzureActivity logs (Administrative category) to identify the deletion of an Microsoft Entra ID Hybrid Health AD FS service instance in a tenant.
  A threat actor can create a new AD Health ADFS service and create a fake server to spoof AD FS signing logs.
  The health AD FS service can then be deleted after it is no longer needed via HTTP requests to Azure.
  More information is available in this blog https://o365blog.com/post/hybridhealthagent/'
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1578.003
tags:
  - SimuLand
query: |
  AzureActivity
  | where CategoryValue =~ 'Administrative'
  | where ResourceProviderValue =~ 'Microsoft.ADHybridHealthService'
  | where _ResourceId has 'AdFederationService'
  | where OperationNameValue =~ 'Microsoft.ADHybridHealthService/services/delete'
  | extend claimsJson = parse_json(Claims)
  | extend AppId = tostring(claimsJson.appid), AccountName = tostring(claimsJson.name), Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
  | project-away claimsJson
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 2.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

AzureActivity

Stage 2: where

| where CategoryValue =~ 'Administrative'

Stage 3: where

| where ResourceProviderValue =~ 'Microsoft.ADHybridHealthService'

Stage 4: where

| where _ResourceId has 'AdFederationService'

Stage 5: where

| where OperationNameValue =~ 'Microsoft.ADHybridHealthService/services/delete'

Stage 6: extend

| extend claimsJson = parse_json(Claims)

Stage 7: extend

| extend AppId = tostring(claimsJson.appid), AccountName = tostring(claimsJson.name), Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])

Stage 8: project-away

| project-away claimsJson

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AppIdextend
Nameextend
UPNSuffixextend