Detection rules › Kusto
Detect DNS queries reporting multiple errors from different clients - Static threshold based (ASIM DNS Solution)
This rule creates an alert when multiple clients report errors for the same DNS query. This helps in identifying possible similar C2 communications originating from different clients. It utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Command & Control | T1008 Fallback Channels, T1568 Dynamic Resolution, T1573 Encrypted Channel |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Sysmon | Event ID 22 | DNSEvent (DNS query) |
Rule body kusto
id: 5b8344eb-fa28-4ac3-bcff-bc19d5d63089
name: Detect DNS queries reporting multiple errors from different clients - Static threshold based (ASIM DNS Solution)
description: |
'This rule creates an alert when multiple clients report errors for the same DNS query. This helps in identifying possible similar C2 communications originating from different clients. It utilizes [ASIM](https://aka.ms/AboutASIM) normalization and is applied to any source that supports the ASIM DNS schema.'
severity: Medium
status: Available
tags:
- Schema: ASimDns
SchemaVersion: 0.1.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1568
- T1573
- T1008
query: |
let lookback=1h;
let threshold = 2;
let errors = dynamic(['NXDOMAIN', 'SERVFAIL', 'REFUSED']);
_Im_Dns(starttime=ago(lookback))
| where EventResultDetails has_any (errors)
| summarize SrcIPs = make_set(SrcIpAddr, 100), Dvcs = make_set(Dvc, 100), ResourceIds = make_set(_ResourceId, 100) by DnsQuery, bin(TimeGenerated, 10min)
| where array_length(SrcIPs) >= threshold
| extend TotalIPs = array_length(SrcIPs),IPCountthreshold = threshold
| extend DomainName = strcat(split(DnsQuery, ".")[1], ".", split(DnsQuery, ".")[2])
| mv-expand SrcIPs
| extend SrcIP = tostring(SrcIPs)
| mv-expand Dvcs
| extend Dvc = tostring(Dvcs)
| mv-expand ResourceIds
| extend ResourceId = tostring(ResourceIds)
| extend Dvc = strcat(split(Dvc, ".")[0])
| summarize Start=min(TimeGenerated), End=max(TimeGenerated) by SrcIP, Dvc, ResourceId, DnsQuery, DomainName, SrcIPs = tostring(SrcIPs), IPCountthreshold = threshold, TotalIPs
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DnsQuery
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIP
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: ResourceId
- entityType: Url
fieldMappings:
- identifier: Url
columnName: DnsQuery
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
eventGroupingSettings:
aggregationKind: SingleAlert
customDetails:
SrcIPs: SrcIPs
IPCountthreshold: IPCountthreshold
TotalIPs: TotalIPs
alertDetailsOverride:
alertDisplayNameFormat: "[Static threshold] Multiple errors for the same DNS query has been detected - '{{DnsQuery}}'"
alertDescriptionFormat: "Multiple errors were detected on different clients for the same DNS query. These unsuccessful responses can be an indication of C2 communication. \n\nThreshold for total clients reporting errors: '{{IPCountthreshold}}'\n\nCurrent count of clients reporting errors for this DNS query: '{{TotalIPs}}'\n\nClients requesting this DNSQuery include:\n\n'{{SrcIPs}}'"
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 1h;
let threshold = 2;
let errors = dynamic(['NXDOMAIN', 'SERVFAIL', 'REFUSED']);
Stage 1: source
_Im_Dns(starttime=ago(lookback))
Stage 2: where
| where EventResultDetails has_any (errors)
Stage 3: summarize
| summarize SrcIPs = make_set(SrcIpAddr, 100), Dvcs = make_set(Dvc, 100), ResourceIds = make_set(_ResourceId, 100) by DnsQuery, bin(TimeGenerated, 10min)
Stage 4: where
| where array_length(SrcIPs) >= threshold
Stage 5: extend
| extend TotalIPs = array_length(SrcIPs),IPCountthreshold = threshold
Stage 6: extend
| extend DomainName = strcat(split(DnsQuery, ".")[1], ".", split(DnsQuery, ".")[2])
Stage 7: mv-expand
| mv-expand SrcIPs
Stage 8: extend
| extend SrcIP = tostring(SrcIPs)
Stage 9: mv-expand
| mv-expand Dvcs
Stage 10: extend
| extend Dvc = tostring(Dvcs)
Stage 11: mv-expand
| mv-expand ResourceIds
Stage 12: extend
| extend ResourceId = tostring(ResourceIds)
Stage 13: extend
| extend Dvc = strcat(split(Dvc, ".")[0])
Stage 14: summarize
| summarize Start=min(TimeGenerated), End=max(TimeGenerated) by SrcIP, Dvc, ResourceId, DnsQuery, DomainName, SrcIPs = tostring(SrcIPs), IPCountthreshold = threshold, TotalIPs
Stage 15: extend
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
Stage 16: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
HostNameDomain =DomainIndex != -1substring(Dvc, (DomainIndex + 1))DvcIndicators
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 |
|---|---|---|
EventResultDetails | match |
|
SrcIPs | ge |
|
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 |
|---|---|
DnsQuery | summarize |
DomainName | summarize |
Dvc | summarize |
End | summarize |
IPCountthreshold | summarize |
ResourceId | summarize |
SrcIP | summarize |
SrcIPs | summarize |
Start | summarize |
TotalIPs | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |