Detection rules › Kusto

Hunt for Defender for Identity not installed but eligible

Group by
MdiDeviceName, NodeName
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This query shows you which servers are eligible for Defender for identity but does not have the Defender for Identity agent installed. The query seach the eligible servers via Defender for Endpoint (requirement for this query to work), and is based on the server roles that MDE recongnizes.

Rule body yaml

let device_roles = dynamic(["EntraConnectServer", "AzureADConnectServer", "ActiveDirectoryCertificateServicesServer", "DomainController", "ADFS"]);
let mdi_servers = (
    DeviceTvmSoftwareInventory
    | where SoftwareName == "azure_advanced_threat_protection_sensor"
    | distinct MdiDeviceName=tolower(DeviceName)
);
let mdi_eligible_servers = (
    ExposureGraphNodes
    | extend DeviceRoles= parse_json(NodeProperties)["rawData"]["deviceRole"]
    | extend CriticalityRuleNames = parse_json(NodeProperties)["rawData"]["criticalityLevel"]["ruleNames"]
    | where DeviceRoles has_any (device_roles) or
        CriticalityRuleNames has_any (device_roles)
    | distinct NodeName=tolower(NodeName), tostring(DeviceRoles), tostring(CriticalityRuleNames)
);
mdi_servers
| join kind=rightouter mdi_eligible_servers on $left.MdiDeviceName == $right.NodeName
| extend Issue = iff(isempty(MdiDeviceName), "This server is eligible for MDI but does not have MDI installed", "None")
| where Issue != "None"

Stages and Predicates

Parameters

let device_roles = dynamic(["EntraConnectServer", "AzureADConnectServer", "ActiveDirectoryCertificateServicesServer", "DomainController", "ADFS"]);

Let binding: mdi_eligible_servers

let mdi_eligible_servers = (
    ExposureGraphNodes
    | extend DeviceRoles= parse_json(NodeProperties)["rawData"]["deviceRole"]
    | extend CriticalityRuleNames = parse_json(NodeProperties)["rawData"]["criticalityLevel"]["ruleNames"]
    | where DeviceRoles has_any (device_roles) or
        CriticalityRuleNames has_any (device_roles)
    | distinct NodeName=tolower(NodeName), tostring(DeviceRoles), tostring(CriticalityRuleNames)
);

Derived from device_roles.

The stages below define let mdi_servers (the rule's main pipeline source).

Stage 1: source

DeviceTvmSoftwareInventory

Stage 2: where

| where SoftwareName == "azure_advanced_threat_protection_sensor"

Stage 3: distinct

| distinct MdiDeviceName=tolower(DeviceName)

The stages below run on mdi_servers (the outer pipeline).

Stage 4: join

mdi_servers
| join kind=rightouter mdi_eligible_servers on $left.MdiDeviceName == $right.NodeName

Stage 5: extend

| extend Issue = iff(isempty(MdiDeviceName), "This server is eligible for MDI but does not have MDI installed", "None")
Issue =
ifisempty(MdiDeviceName)"This server is eligible for MDI but does not have MDI installed"
else"None"

Stage 6: where

| where Issue != "None"

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
CriticalityRuleNamesmatch
  • ADFS
  • ActiveDirectoryCertificateServicesServer
  • AzureADConnectServer
  • DomainController
  • EntraConnectServer
DeviceRolesmatch
  • ADFS
  • ActiveDirectoryCertificateServicesServer
  • AzureADConnectServer
  • DomainController
  • EntraConnectServer
Issuene
  • None transforms: cased
SoftwareNameeq
  • azure_advanced_threat_protection_sensor transforms: cased

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
Issueextend