Detection rules › Kusto

Whisper Security - Newly Registered Domain on Threat ASN

Status
available
Severity
high
Time window
1d
Group by
asn, parsedAsn
Source
github.com/Azure/Azure-Sentinel

Identifies newly registered domains (under 7 days old) hosted on autonomous systems with elevated threat scores. This combination often indicates adversary infrastructure staging for phishing, malware delivery, or C2 operations.

MITRE ATT&CK coverage

TacticTechniques
Resource Development

Rule body

id: 4f80ee8e-901f-538a-8603-cc4b49e80164
name: Whisper Security - Newly Registered Domain on Threat ASN
description: |
  Identifies newly registered domains (under 7 days old) hosted on autonomous systems with elevated threat scores. This combination often indicates adversary infrastructure staging for phishing, malware delivery, or C2 operations.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperASNReputation_CL
  - WhisperInfraContext_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
relevantTechniques:
- T1583
query: |
  // MITRE ATT&CK: T1583.001 - Acquire Infrastructure: Domains
  // Tactic: Resource Development
  // Correlates newly registered domains with high-threat ASN reputation data
  let maxDomainAgeDays = 7;
  let asnThreatThreshold = 50;
  // Defensive: WhisperInfraChainPipeline currently emits domainAge=-1 as a
  // sentinel when the create date is unknown (tracked bug - proper fix needs
  // CALL whisper.history() integration). Filter the sentinel out so the rule
  // does not fire on every domain with an ASN.
  let newDomains = WhisperInfraContext_CL
      | where TimeGenerated > ago(1d)
      | where domainAge >= 0 and domainAge < maxDomainAgeDays
      | where isnotempty(asns)
      | extend parsedAsn = tostring(split(asns, ",")[0])
      | extend parsedIp = tostring(split(ipAddresses, ",")[0])
      | project TimeGenerated, indicator, domainAge, parsedAsn, parsedIp, registrar, countries;
  let threatAsns = WhisperASNReputation_CL
      | where TimeGenerated > ago(1d)
      | where maxThreatScore > asnThreatThreshold
      | project asn, asnName, reputationScore, reputationLevel, maxThreatScore;
  newDomains
      | join kind=inner (threatAsns) on $left.parsedAsn == $right.asn
      | extend DnsDomain = indicator, IPAddress = parsedIp
      | project TimeGenerated, DnsDomain, IPAddress, domainAge, registrar, countries, asn, asnName, reputationScore, reputationLevel, maxThreatScore
entityMappings:
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: DnsDomain
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: IPAddress
customDetails:
  DomainAgeDays: domainAge
  ASNName: asnName
  ReputationScore: reputationScore
  MaxThreatScore: maxThreatScore
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let maxDomainAgeDays = 7;
let asnThreatThreshold = 50;

let newDomains is inlined into the numbered stages below.

Let binding: threatAsns used in Stage 8

let threatAsns = WhisperASNReputation_CL
    | where TimeGenerated > ago(1d)
    | where maxThreatScore > asnThreatThreshold
    | project asn, asnName, reputationScore, reputationLevel, maxThreatScore;

Stages 1 to 7 define let newDomains (the rule's main pipeline source); stages 8 to 10 run on it.

Stage 1: source

WhisperInfraContext_CL

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: where

| where domainAge >= 0 and domainAge < maxDomainAgeDays

Stage 4: where

| where isnotempty(asns)

Stage 5: extend

| extend parsedAsn = tostring(split(asns, ",")[0])

Stage 6: extend

| extend parsedIp = tostring(split(ipAddresses, ",")[0])

Stage 7: project

| project TimeGenerated, indicator, domainAge, parsedAsn, parsedIp, registrar, countries

Stage 8: join

newDomains
| join kind=inner (threatAsns) on $left.parsedAsn == $right.asn

Stage 9: extend

| extend DnsDomain = indicator, IPAddress = parsedIp

Stage 10: project

| project TimeGenerated, DnsDomain, IPAddress, domainAge, registrar, countries, asn, asnName, reputationScore, reputationLevel, maxThreatScore

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainproject
IPAddressproject
TimeGeneratedproject
asnproject
asnNameproject
countriesproject
domainAgeproject
maxThreatScoreproject
registrarproject
reputationLevelproject
reputationScoreproject