↳ GitHub sourceAnalytics ruleMedium

PRODAFT USTA - TI map Domain to DnsEvents

Description

'Identifies a match in DnsEvents from any domain-name indicator ingested from PRODAFT USTA IoC Threat Intelligence (SourceSystem starting with "PRODAFT USTA").'
Rule type
Scheduled
Version
1.0.0
Query frequency
1h
Query period
14d
Trigger
gt 0

Declared MITRE coverage

Declared sources

Metadata from the source file. No dependencies inferred from KQL.

Connectors

Data types

KQL query

Original query, unchanged.

let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Domain_Indicators = ThreatIntelIndicators
  // Each USTA IoC feed uploads under its own SourceSystem ("PRODAFT USTA - Malicious URLs", etc.)
  | where SourceSystem startswith "PRODAFT USTA"
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "domain-name"
  | extend DomainName = tolower(ObservableValue)
  | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// TLD allow-list built from the feed: lets the DnsEvents scan discard most rows before the join
let maxListSize = 100000;
let list_tlds = Domain_Indicators
  | extend parts = split(DomainName, '.')
  | extend tld = parts[(array_length(parts)-1)]
  | extend IndicatorId = tostring(split(Id, "--")[2])
  | summarize count() by tostring(tld)
  | project tld
  | summarize make_list(tld, maxListSize);
Domain_Indicators
 | project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
  // innerunique: one match per indicator is enough to alert, and it keeps the join cheap
  | join kind=innerunique (
    DnsEvents
    | where TimeGenerated > ago(dt_lookBack)
    | where isnotempty(Name)
    // DnsEvents.Name keeps the queried casing; the indicator side is lowercased, and the join below is case-sensitive
    | extend Name = tolower(Name)
    | extend parts = split(Name, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | where tld in~ (list_tlds)
    | extend DNS_TimeGenerated = TimeGenerated
  ) on $left.DomainName==$right.Name
  // non-expiring indicators are allowed above, so they must survive this filter too
  | where isempty(ValidUntil) or DNS_TimeGenerated < ValidUntil
  | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by Id, Name
  | extend Description = tostring(parse_json(Data).description)
  | extend IndicatorTags = tostring(parse_json(Data).labels)
  | project DNS_TimeGenerated, Description, IndicatorTags, Id, ValidUntil, Confidence, Computer, ClientIP
  | extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
  | extend timestamp = DNS_TimeGenerated

Declared entities

HostIP

Related content

Links established from declared identifiers and solution manifests.

Source provenance

GitHub

Displayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.

Source identifier
c6f85aff-4ef0-4fde-b90e-e0ba1a9c3df6
Additional source files 2Solutions/PRODAFT USTA - IoC Threat Intelligence/Analytic Rules/TIMapDomainToDnsEvents.yamlsource ↗Solutions/PRODAFT USTA - IoC Threat Intelligence/Data/Solution_PRODAFTUstaIoC.jsonsolution-membership ↗
GSTEP / CATALOG TRACKING

Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC

GSTEP sync dates, separate from the source content’s publication dates.