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.
Telemetry coverage
Rule body
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
let no_credguard_devices
Stage 2: source
ExposureGraphEdges
Stage 3: macro
macro
Stage 4: macro
macro
Stage 5: distinct
distinct SourceNodeId, SourceNodeName, TargetNodeId, TargetNodeName
Stage 6: join
join kind=inner (no_credguard_devices) on SourceNodeId, NodeId
Stage 7: summarize
summarize UserList by DeviceName
Stage 8: extend
extend UserCount
Stage 9: sort
sort by UserCount
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | summarize |
UserList | summarize |
UserCount | extend |