Detection rules › Kusto

Potential DGA detected

Status
available
Severity
medium
Time window
10d
Group by
ClientIP
Source
github.com/Azure/Azure-Sentinel

'Identifies clients with a high NXDomain count, which could be indicative of a DGA (cycling through possible C2 domains where most C2s are not live). Alerts are generated when a new IP address is seen (based on not being associated with NXDomain records in the prior 10-day baseline period).'

MITRE ATT&CK coverage

TacticTechniques
Command & ControlT1008 Fallback Channels, T1568 Dynamic Resolution

Rule body kusto

id: a0907abe-6925-4d90-af2b-c7e89dc201a6
name: Potential DGA detected
description: |
  'Identifies clients with a high NXDomain count, which could be indicative of a DGA (cycling through possible C2 domains where most C2s are not live).
  Alerts are generated when a new IP address is seen (based on not being associated with NXDomain records in the prior 10-day baseline period).'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: DNS
    dataTypes:
      - DnsEvents
queryFrequency: 1d
queryPeriod: 10d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1568
  - T1008
query: |
  let starttime = 10d;
  let endtime = 1d;
  let threshold = 100;
  let nxDomainDnsEvents = DnsEvents
  // ResultCode 3 => 'NXDOMAIN'
  | where ResultCode == 3
  | where QueryType in~ ("A", "AAAA")
  | where ipv4_is_match("127.0.0.1", ClientIP) == False
  | where Name !has "/"
  | where Name has ".";
  nxDomainDnsEvents
  | where TimeGenerated > ago(endtime)
  // sld = Second Level Domain
  | extend sld = tostring(split(Name, ".")[-2])
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), dcount(sld), sampleNXDomainList=make_set(Name, 100) by ClientIP
  | where dcount_sld > threshold
  // Filter out previously seen IPs
  // Returns all the records from the left side that don't have matches from the right
  | join kind=leftanti (nxDomainDnsEvents
      | where TimeGenerated between(ago(starttime)..ago(endtime))
      | extend sld = tostring(split(Name, ".")[-2])
      | summarize dcount(sld) by ClientIP, bin(TimeGenerated,1d)
      | where dcount_sld > threshold
      ) on ClientIP
      | order by dcount_sld desc
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 10d;
let endtime = 1d;
let threshold = 100;

The stages below define let nxDomainDnsEvents (the rule's main pipeline source).

Stage 1: source

DnsEvents

Stage 2: where

| where ResultCode == 3

Stage 3: where

| where QueryType in~ ("A", "AAAA")

Stage 4: where

| where ipv4_is_match("127.0.0.1", ClientIP) == False

Stage 5: where

| where Name !has "/"

Stage 6: where

| where Name has "."

The stages below run on nxDomainDnsEvents (the outer pipeline).

Stage 7: where

nxDomainDnsEvents
| where TimeGenerated > ago(endtime)

Stage 8: extend

| extend sld = tostring(split(Name, ".")[-2])

Stage 9: summarize

| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), dcount(sld), sampleNXDomainList=make_set(Name, 100) by ClientIP

Stage 10: where

| where dcount_sld > threshold

Stage 11: join (negated)

| join kind=leftanti (nxDomainDnsEvents
    | where TimeGenerated between(ago(starttime)..ago(endtime))
    | extend sld = tostring(split(Name, ".")[-2])
    | summarize dcount(sld) by ClientIP, bin(TimeGenerated,1d)
    | where dcount_sld > threshold
    ) on ClientIP

Stage 12: sort

| order by dcount_sld desc

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
ClientIPcidr_match127.0.0.1
Namematch/
Namematch.
QueryTypeinA, AAAA
ResultCodeeq3
dcount_sldgt100

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.

FieldKindValues
ClientIPcidr_match
  • 127.0.0.1
Namematch
  • . transforms: term
  • / transforms: term
QueryTypein
  • A
  • AAAA
ResultCodeeq
  • 3 transforms: cased
dcount_sldgt
  • 100 transforms: cased

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.

FieldSource
ClientIPsummarize
EndTimeUtcsummarize
StartTimeUtcsummarize
sampleNXDomainListsummarize