95 строки
5.1 KiB
YAML
95 строки
5.1 KiB
YAML
id: 543e1ec6-ee5e-4368-aaa6-405f0551ba5c
|
|
name: Potential DGA detected
|
|
description: |
|
|
'Clients with a high NXDomain count could be indicative of a DGA (cycling through possible C2 domains
|
|
where most C2s are not live). Based on quartile precent analysis aglorithm.'
|
|
severity: Medium
|
|
requiredDataConnectors:
|
|
- connectorId: DNS
|
|
dataTypes:
|
|
- DnsEvents
|
|
queryFrequency: 1d
|
|
queryPeriod: 10d
|
|
triggerOperator: gt
|
|
triggerThreshold: 0
|
|
tactics:
|
|
- CommandAndControl
|
|
relevantTechniques:
|
|
- T1483
|
|
- T1008
|
|
query: |
|
|
|
|
let timeframe = 1d;
|
|
let excludeTLD = dynamic(["arris","ati","virtusa","unknowndomain","onion","corp","domain","local","localdomain","host","home","gateway","lan",
|
|
"services","hub","domain.name","WirelessAP","Digicom-ADSL","OpenDNS","dlinkrouter","Dlink","ASUS","device","router","Belkin","DHCP","Cisco"]);
|
|
let nxDomainDnsEvents = DnsEvents
|
|
| where ResultCode == 3
|
|
| where QueryType in ("A", "AAAA")
|
|
| where ipv4_is_match("127.0.0.1", ClientIP) == False
|
|
| where Name !contains "/"
|
|
| where Name contains "."
|
|
| extend mytld = tostring(split(Name, '.')[-1])
|
|
| where mytld !in~ (excludeTLD)
|
|
| extend truncatedDomain = iff((strlen(Name) - indexof(Name, tostring(split(Name, ".")[-2])) ) >= 7,
|
|
strcat(tostring(split(Name, ".")[-2]), ".", tostring(split(Name, ".")[-1])) ,
|
|
strcat(tostring(split(Name, ".")[-3]), ".", tostring(split(Name, ".")[-2]), ".", tostring(split(Name, ".")[-1])));
|
|
let quartileFunctionForIPThreshold = view (mypercentile:long, startTimeSpan:timespan, endTimeSpan:timespan) {
|
|
(nxDomainDnsEvents
|
|
| where TimeGenerated between (ago(startTimeSpan)..ago(endTimeSpan))
|
|
| summarize domainCount = dcount(truncatedDomain) by ClientIP, bin(TimeGenerated, 1d)
|
|
| project SearchList = (domainCount), ClientIP
|
|
| summarize qPercentiles = percentiles(SearchList, mypercentile) by ClientIP);
|
|
};
|
|
let firstQT = quartileFunctionForIPThreshold(25, 7d, 2d) | project-rename percentile_SearchList_25 = qPercentiles;
|
|
let thirdQT = quartileFunctionForIPThreshold(75, 7d, 2d) | project-rename percentile_SearchList_75 = qPercentiles;
|
|
// The IP threshold could be adjusted for based on the skewness of the IPthreshold distribution per IP - https://wis.kuleuven.be/stat/robust/papers/2008/outlierdetectionskeweddata-revision.pdf
|
|
let threshold = (firstQT
|
|
| join thirdQT on ClientIP
|
|
| extend IPthreshold = percentile_SearchList_75 + (1.5*exp(3)*(percentile_SearchList_75 - percentile_SearchList_25))
|
|
| project ClientIP, IPthreshold);
|
|
let FilterOnIPThreshold_MainTable = (
|
|
nxDomainDnsEvents
|
|
| where TimeGenerated > ago(timeframe)
|
|
| summarize TotalNXLookups=dcount(truncatedDomain) by ClientIP
|
|
| sort by TotalNXLookups desc
|
|
| join ['threshold'] on ClientIP
|
|
// Comment the line below in order to view results filtered by Global Threshold only.
|
|
| where TotalNXLookups > IPthreshold
|
|
| join kind = leftouter (nxDomainDnsEvents
|
|
| where TimeGenerated > ago(timeframe)
|
|
| summarize domainCount = dcount(Name) by truncatedDomain, ClientIP
|
|
| project SearchList = strcat(truncatedDomain," (",tostring(domainCount),")"), ClientIP
|
|
) on ClientIP
|
|
| summarize SLDs_DistinctLookups = make_list(SearchList) by ClientIP, TotalNXLookups, IPthreshold
|
|
| sort by TotalNXLookups desc);
|
|
//
|
|
let quartileFunctionForGlobalThreshold = view (mypercentile:long, startTimeSpan:timespan) {
|
|
(nxDomainDnsEvents
|
|
| where TimeGenerated > ago(startTimeSpan)
|
|
| summarize domainCount = dcount(truncatedDomain) by ClientIP
|
|
| summarize event_count = count() by domainCount
|
|
| summarize perc2 = percentilesw(domainCount, event_count, mypercentile));
|
|
};
|
|
let firstQ = toscalar(quartileFunctionForGlobalThreshold(25, 1d));
|
|
let thirdQ = toscalar(quartileFunctionForGlobalThreshold(75, 1d));
|
|
// The Global threshold could be adjusted for based on the skewness of the GlobalThreshold distribution per IP - https://wis.kuleuven.be/stat/robust/papers/2008/outlierdetectionskeweddata-revision.pdf
|
|
let GlobalThreshold = toscalar(thirdQ + (1.5*exp(3)*(thirdQ - firstQ)));
|
|
let FilterOnGlobalThreshold_MainTable = (
|
|
nxDomainDnsEvents
|
|
| where TimeGenerated > ago(timeframe)
|
|
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), TotalNXLookups = dcount(truncatedDomain) by ClientIP
|
|
| sort by TotalNXLookups desc
|
|
// Comment the line below in order to view results filtered by IPThreshold only.
|
|
| where TotalNXLookups > GlobalThreshold
|
|
| join kind = leftouter (nxDomainDnsEvents
|
|
| where TimeGenerated > ago(timeframe)
|
|
| summarize domainCount = dcount(Name) by truncatedDomain, ClientIP
|
|
| project truncatedDomain = strcat(truncatedDomain," (",tostring(domainCount),")"), ClientIP
|
|
) on ClientIP
|
|
| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), SLDs_DistinctLookups = make_list(truncatedDomain), UniqueSLDsCount=count(truncatedDomain) by ClientIP, TotalNXLookups, GlobalThreshold
|
|
| sort by TotalNXLookups desc);
|
|
FilterOnIPThreshold_MainTable
|
|
| join FilterOnGlobalThreshold_MainTable on ClientIP
|
|
| project StartTimeUtc, EndTimeUtc, ClientIP, TotalNXLookups, IPthreshold, GlobalThreshold, SLDs_DistinctLookups, UniqueSLDsCount
|
|
| extend timestamp = StartTimeUtc, IPCustomEntity = ClientIP
|