Detection rules › Kusto
Rare client observed with high reverse DNS lookup count
'Identifies clients with a high reverse DNS counts that could be carrying out reconnaissance or discovery activity. Alerts are generated if the IP performing such reverse DNS lookups was not seen doing so in the preceding 7-day period.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Discovery | T1046 Network Service Discovery |
Rule body kusto
id: 15ae38a2-2e29-48f7-883f-863fb25a5a06
name: Rare client observed with high reverse DNS lookup count
description: |
'Identifies clients with a high reverse DNS counts that could be carrying out reconnaissance or discovery activity.
Alerts are generated if the IP performing such reverse DNS lookups was not seen doing so in the preceding 7-day period.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: DNS
dataTypes:
- DnsEvents
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1046
query: |
let starttime = 8d;
let endtime = 1d;
let threshold = 10;
DnsEvents
| where TimeGenerated > ago(endtime)
| where Name has "in-addr.arpa"
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), dcount(Name), ReverseDNSLookup_List = make_set(Name,100) by ClientIP
| where dcount_Name > threshold
| project StartTimeUtc, EndTimeUtc, ClientIP , dcount_Name, ReverseDNSLookup_List
// Filter out previously seen IPs
// Returns all the records from the left side that don't have matches from the right
| join kind=leftanti (DnsEvents
| where TimeGenerated between(ago(starttime)..ago(endtime))
| where Name has "in-addr.arpa"
| summarize dcount(Name) by ClientIP, bin(TimeGenerated, 1d)
| where dcount_Name > threshold
| project ClientIP , dcount_Name
) on ClientIP
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 8d;
let endtime = 1d;
let threshold = 10;
Stage 1: source
DnsEvents
Stage 2: where
| where TimeGenerated > ago(endtime)
Stage 3: where
| where Name has "in-addr.arpa"
Stage 4: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), dcount(Name), ReverseDNSLookup_List = make_set(Name,100) by ClientIP
Stage 5: where
| where dcount_Name > threshold
Stage 6: project
| project StartTimeUtc, EndTimeUtc, ClientIP , dcount_Name, ReverseDNSLookup_List
Stage 7: join (negated)
| join kind=leftanti (DnsEvents
| where TimeGenerated between(ago(starttime)..ago(endtime))
| where Name has "in-addr.arpa"
| summarize dcount(Name) by ClientIP, bin(TimeGenerated, 1d)
| where dcount_Name > threshold
| project ClientIP , dcount_Name
) on ClientIP
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
Name | match | in-addr.arpa |
dcount_Name | gt | 10 |
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 |
|---|---|---|
Name | match |
|
dcount_Name | 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 |
|---|---|
ClientIP | project |
EndTimeUtc | project |
ReverseDNSLookup_List | project |
StartTimeUtc | project |
dcount_Name | project |