Detection rules › Kusto
Hunt for critical credentials on non Credential Guard enabled devices
This query searches for devices that does not have Credential Guard enabled but contains critical credentials. The output shows for how many users each non Credential Guard device has credentials, together with the list of users being exposed.
Rule body yaml
let no_credguard_devices = (
ExposureGraphNodes
// Get devices with credential guard misconfiguration
| where array_length(NodeProperties.rawData.hasGuardMisconfigurations) > 0
// Get interesting data
| extend DeviceName = tostring(parse_json(NodeProperties)["rawData"]["deviceName"]),
DeviceId = tostring(EntityIds.id)
| extend DeviceName = iff(isempty(DeviceName), NodeName, DeviceName)
// Search for distinct devices
| distinct NodeId, DeviceName
);
let critical_users = toscalar(
// Search for critical users
ExposureGraphNodes
| where NodeLabel == "user"
| extend CriticalityLevel = todynamic(NodeProperties).rawData.criticalityLevel.criticalityLevel
| extend RuleNames = todynamic(NodeProperties).rawData.criticalityLevel.ruleNames
| where CriticalityLevel == 0
| distinct NodeName, NodeId, tostring(CriticalityLevel), tostring(RuleNames)
| summarize make_set(NodeName)
);
// Make graph for max of 3 edges, where we start from a device and end with an user
ExposureGraphEdges
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
where SourceNode.NodeLabel in ("device", "microsoft.compute/virtualmachines") and TargetNode.NodeLabel == "user" and TargetNode.NodeName in ( critical_users )
project SourceNodeName = SourceNode.NodeName,
SourceNodeId = SourceNode.NodeId,
Edges = anyEdge.EdgeLabel,
TargetNodeId = TargetNode.NodeId,
TargetNodeName = TargetNode.NodeName,
TargetNodeLabel = TargetNode.NodeLabel,
TargetCriticalityLevel = TargetNode.NodeProperties.rawData.criticalityLevel.criticalityLevel,
TargetRuleNames = TargetNode.NodeProperties.rawData.criticalityLevel.ruleNames
| distinct SourceNodeId, SourceNodeName, TargetNodeId, TargetNodeName, tostring(TargetCriticalityLevel), tostring(TargetRuleNames)
// Only return devices that does not have a TPM fully enabled
| join kind=inner no_credguard_devices on $left.SourceNodeId == $right.NodeId
// Make list of users per device
| summarize UserList = make_list(TargetNodeName) by DeviceName
// Count amount of exposed users per device
| extend UserCount = array_length(UserList)
| sort by UserCount desc
Stages and Predicates
Let binding: no_credguard_devices
let no_credguard_devices = (
ExposureGraphNodes
| where array_length(NodeProperties.rawData.hasGuardMisconfigurations) > 0
| extend DeviceName = tostring(parse_json(NodeProperties)["rawData"]["deviceName"]),
DeviceId = tostring(EntityIds.id)
| extend DeviceName = iff(isempty(DeviceName), NodeName, DeviceName)
| distinct NodeId, DeviceName
);
Let binding: critical_users
let critical_users = toscalar(
ExposureGraphNodes
| where NodeLabel == "user"
| extend CriticalityLevel = todynamic(NodeProperties).rawData.criticalityLevel.criticalityLevel
| extend RuleNames = todynamic(NodeProperties).rawData.criticalityLevel.ruleNames
| where CriticalityLevel == 0
| distinct NodeName, NodeId, tostring(CriticalityLevel), tostring(RuleNames)
| summarize make_set(NodeName)
);
Stage 1: source
ExposureGraphEdges
Stage 2: macro
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
Stage 3: macro
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
where SourceNode.NodeLabel in ("device", "microsoft.compute/virtualmachines") and TargetNode.NodeLabel == "user" and TargetNode.NodeName in ( critical_users )
project SourceNodeName = SourceNode.NodeName,
SourceNodeId = SourceNode.NodeId,
Edges = anyEdge.EdgeLabel,
TargetNodeId = TargetNode.NodeId,
TargetNodeName = TargetNode.NodeName,
TargetNodeLabel = TargetNode.NodeLabel,
TargetCriticalityLevel = TargetNode.NodeProperties.rawData.criticalityLevel.criticalityLevel,
TargetRuleNames = TargetNode.NodeProperties.rawData.criticalityLevel.ruleNames
Stage 4: distinct
| distinct SourceNodeId, SourceNodeName, TargetNodeId, TargetNodeName, tostring(TargetCriticalityLevel), tostring(TargetRuleNames)
Stage 5: join
| join kind=inner no_credguard_devices on $left.SourceNodeId == $right.NodeId
Stage 6: summarize
| summarize UserList = make_list(TargetNodeName) by DeviceName
Stage 7: extend
| extend UserCount = array_length(UserList)
Stage 8: sort
| sort by UserCount desc
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.
| Field | Kind | Values |
|---|---|---|
hasGuardMisconfigurations | gt |
|
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.
| Field | Source |
|---|---|
DeviceName | summarize |
UserList | summarize |
UserCount | extend |