Detection rules › Kusto
Hunt for privilege escalation paths with high ACLs
When an adversary establishes data collection of an Active Directory domain, they regularly search for interesting accounts with privilege escalation paths using the genericWrite and genericAll ACL permissions on objects. When using BloodHound, it is very easy to get a visual overview of these paths in an Active Directory domain. This query tries to establish the same using Defender XDR Exposure Management.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | ExposureGraphEdges action any: Exposure graph edges |
Rule body
let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
// Get users and groups with high ACL permissions on other objects
let HighPermissionLinks = (ExposureGraphEdges
// Get edges related to roles
| where EdgeLabel == "has role on"
// Get edges containing high permission ACLs
| extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
| where Permissions has_any (high_permissions)
// Exclude Domain and Enterprise Administrators as source node
| where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
| summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
let HighPermissionNodes = toscalar(
HighPermissionLinks
| summarize SourceNodes = make_set(SourceNodeName)
);
// Get edges for links to the high ACL permissions
ExposureGraphEdges
| where TargetNodeName in (HighPermissionNodes)
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
// Get between one and three relations
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
project IncomingNodeName = SourceNode.NodeName,
IncomingNodeLabel = SourceNode.NodeLabel,
Edges = anyEdge.EdgeLabel,
OutgoingNodeName = TargetNode.NodeName,
OutgoingNodeId = TargetNode.NodeId
// Filter for interesting edges
| where Edges has_any (edge_labels)
// Join the high permission ACLs
| join kind=inner HighPermissionLinks on $left.OutgoingNodeId == $right.SourceNodeId
// Exclude Domain and Enterprise Administrators as source node
| where not(IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(IncomingNodeLabel == "user" and IncomingNodeName == "Administrator")
| distinct IncomingNodeName, IncomingNodeLabel, tostring(Edges), OutgoingNodeName, OutgoingNodeLabel = SourceNodeLabel, tostring(Permissions), TargetNodeLabel, tostring(TargetNodes), TargetNodeCount
Stages and Predicates
Parameters
let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
Let binding: HighPermissionLinks
let HighPermissionLinks = (ExposureGraphEdges
| where EdgeLabel == "has role on"
| extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
| where Permissions has_any (high_permissions)
| where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
| where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
| summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
Let binding: HighPermissionNodes
let HighPermissionNodes = toscalar(
HighPermissionLinks
| summarize SourceNodes = make_set(SourceNodeName)
);
Stage 1: source
let HighPermissionLinks
Stage 2: source
ExposureGraphEdges
Stage 3: where
where TargetNodeName =~ "HighPermissionNodes"
Stage 4: macro
macro
Stage 5: macro
macro
Stage 6: where
where (Edges contains "member of" or Edges contains "has permissions to" or Edges contains "can authenticate to" or Edges contains "can authenticate as" or Edges contains "has credentials of" or Edges contains "can impersonate as")
Stage 7: join
join kind=inner (HighPermissionLinks) on OutgoingNodeId, SourceNodeId
Stage 8: where
where not ((IncomingNodeLabel =~ "group" and IncomingNodeName in~ ("Domain Admins", "Enterprise Admins")))
Stage 9: where
where not ((IncomingNodeLabel =~ "user" and IncomingNodeName =~ "Administrator"))
Stage 10: distinct
distinct IncomingNodeLabel, IncomingNodeName, OutgoingNodeName, TargetNodeCount, TargetNodeLabel
Stage 11: summarize aggregation inside the join branch
summarize by SourceNodeName, SourceNodeLabel, TargetNodeLabel, SourceNodeId
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SourceNodeLabel | eq | group | excludes:SourceNodeLabel field:"SourceNodeLabel" value:"group" |
SourceNodeName | in | Domain Admins, Enterprise Admins | excludes:SourceNodeName field:"SourceNodeName" value:"Domain Admins" field:"SourceNodeName" value:"Enterprise Admins" |
SourceNodeLabel | eq | user | excludes:SourceNodeLabel field:"SourceNodeLabel" value:"user" |
SourceNodeName | eq | Administrator | excludes:SourceNodeName field:"SourceNodeName" value:"Administrator" |
IncomingNodeLabel | eq | group | excludes:IncomingNodeLabel field:"IncomingNodeLabel" value:"group" |
IncomingNodeName | in | Domain Admins, Enterprise Admins | excludes:IncomingNodeName field:"IncomingNodeName" value:"Domain Admins" field:"IncomingNodeName" value:"Enterprise Admins" |
IncomingNodeLabel | eq | user | excludes:IncomingNodeLabel field:"IncomingNodeLabel" value:"user" |
IncomingNodeName | eq | Administrator | excludes:IncomingNodeName field:"IncomingNodeName" value:"Administrator" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EdgeLabel | eq |
| field:"EdgeLabel" kind:eq value:"has role on" |
Edges | match |
| field:"Edges" kind:match |
Permissions | match |
| field:"Permissions" kind:match |
TargetNodeName | in |
| field:"TargetNodeName" kind:in value:"HighPermissionNodes" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceNodeId | summarize |
SourceNodeLabel | summarize |
SourceNodeName | summarize |
TargetNodeLabel | summarize |