Detection rules › Kusto

Potential DGA(Domain Generation Algorithm) detected via Repetitive Failures - Static threshold based (ASIM DNS Solution)

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

This rule 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). An alert is generated when a new IP address is seen (based on not being seen associated with NXDomain records in prior 10-day baseline period). It utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema.

MITRE ATT&CK coverage

TacticTechniques
Command & Control

Telemetry coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule body

id: 89ba52fa-96a7-4653-829a-ca49bb13336c
name: Potential DGA(Domain Generation Algorithm) detected via Repetitive Failures - Static threshold based (ASIM DNS Solution)
description: |
  'This rule 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). An alert is generated when a new IP address is seen (based on not being seen associated with NXDomain records in prior 10-day baseline period). 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: 1d
queryPeriod: 10d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1568
  - T1008
query: |
  let threshold = 100;
  let lookback = 10d;
  let referenceendtime = 1d;
  let nxDomainDnsEvents = (stime: datetime, etime: datetime) {
    _Im_Dns(responsecodename='NXDOMAIN', starttime=stime, endtime=etime)
    | where DnsQueryTypeName in ("A", "AAAA")
    | where ipv4_is_match("127.0.0.1", SrcIpAddr) == False
    | where DnsQuery !contains "/" and DnsQuery contains "."
  };
  nxDomainDnsEvents (stime=ago(referenceendtime), etime=now())
  | summarize
    StartTimeUtc = min(TimeGenerated),
    EndTimeUtc = max(TimeGenerated),
    DNSQueryCount=dcount(DnsQuery)
    by SrcIpAddr
  | where DNSQueryCount > threshold
  // Filter out previously seen IPs
  | join kind=leftanti (nxDomainDnsEvents (stime=ago(lookback), etime=ago(referenceendtime))
    | summarize DNSQueryCount=dcount(DnsQuery) by SrcIpAddr, bin(TimeGenerated,1d)
    | where DNSQueryCount > threshold)
    on SrcIpAddr
  // Pull out sample NXDomain responses for those remaining potentially infected IPs
  | join kind = inner (nxDomainDnsEvents (stime=ago(lookback), etime=now())
    | summarize by DnsQuery, SrcIpAddr)
    on SrcIpAddr
  | summarize
    StartTimeUtc = min(StartTimeUtc),
    EndTimeUtc = max(EndTimeUtc),
    DNSQueries=make_list(DnsQuery, 100)
    by SrcIpAddr, DNSQueryCount
  | extend DNSQueryThreshold=threshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  DNSQueries: DNSQueries
  DNSQueryThreshold: DNSQueryThreshold
  DNSQueryCount: DNSQueryCount
alertDetailsOverride:
  alertDisplayNameFormat: "[Static threshold] Potential DGA (Domain Generation Algorithm) originating from client IP: '{{SrcIpAddr}}' has been detected."
  alertDescriptionFormat: "Client has been identified with high NXDomain count which could be indicative of a DGA (cycling through possible C2 domains where most C2s are not live). This client is found to be communicating with multiple Domains which do not exist.\n\nDGA DNS query count baseline is: '{{DNSQueryThreshold}}'\n\nCurrent failed DNS query count from this client: '{{DNSQueryCount}}'\n\nDNS queries requested by this client inlcude: '{{DNSQueries}}'"
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 100;
let lookback = 10d;
let referenceendtime = 1d;

let nxDomainDnsEvents is inlined into the numbered stages below.

Stages 1 to 4 define let nxDomainDnsEvents (the rule's main pipeline source); stages 5 to 10 run on it.

Stage 1: source

_Im_Dns(responsecodename='NXDOMAIN', starttime=stime, endtime=etime)

Stage 2: where

| where DnsQueryTypeName in ("A", "AAAA")

Stage 3: where

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

Stage 4: where

| where DnsQuery !contains "/" and DnsQuery contains "."

Stage 5: summarize

nxDomainDnsEvents
| summarize
  StartTimeUtc = min(TimeGenerated),
  EndTimeUtc = max(TimeGenerated),
  DNSQueryCount=dcount(DnsQuery)
  by SrcIpAddr

Stage 6: where

| where DNSQueryCount > threshold

Stage 7: join (negated)

| join kind=leftanti (nxDomainDnsEvents (stime=ago(lookback), etime=ago(referenceendtime))
  | summarize DNSQueryCount=dcount(DnsQuery) by SrcIpAddr, bin(TimeGenerated,1d)
  | where DNSQueryCount > threshold)
  on SrcIpAddr

Stage 8: join

| join kind = inner (nxDomainDnsEvents (stime=ago(lookback), etime=now())
  | summarize by DnsQuery, SrcIpAddr)
  on SrcIpAddr

Stage 9: summarize

| summarize
  StartTimeUtc = min(StartTimeUtc),
  EndTimeUtc = max(EndTimeUtc),
  DNSQueries=make_list(DnsQuery, 100)
  by SrcIpAddr, DNSQueryCount

Stage 10: extend

| extend DNSQueryThreshold=threshold

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DNSQueriessummarize
DNSQueryCountsummarize
EndTimeUtcsummarize
SrcIpAddrsummarize
StartTimeUtcsummarize
DNSQueryThresholdextend