Detection rules › Kusto

TI map Domain entity to Syslog

Severity
medium
Time window
14d
Group by
IndicatorId, TI_DomainEntity, domain
Source
github.com/Azure/Azure-Sentinel

Identifies a match in Syslog table from any Domain IOC from TI

MITRE ATT&CK coverage

TacticTechniques
Command & Control

Rule body

id: 532f62c1-fba6-4baa-bbb6-4a32a4ef32fa
name: TI map Domain entity to Syslog
description: |
  Identifies a match in Syslog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: Syslog
    dataTypes:
      - Syslog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;  // Define the time range to look back for syslog data (1 hour)
  let ioc_lookBack = 14d;  // Define the time range to look back for threat intelligence indicators (14 days)
  // Create a list of top-level domains (TLDs) from the threat feed for later validation
  let list_tlds = ThreatIntelligenceIndicator
    | where isnotempty(DomainName)
    | where TimeGenerated > ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | extend parts = split(DomainName, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | summarize count() by tostring(tld)
    | summarize make_list(tld);
  // Fetch the latest active domain indicators from the threat intelligence data within the specified time range
  let Domain_Indicators = ThreatIntelligenceIndicator
    | where isnotempty(DomainName)
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | extend TI_DomainEntity = DomainName;
  // Join the threat intelligence indicators with syslog data on matching domain entities
  Domain_Indicators
    | join kind=innerunique (
      Syslog
      | where TimeGenerated > ago(dt_lookBack)
      // Extract domain patterns from syslog messages
      | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
      | where isnotempty(domain)
      | extend parts = split(domain, '.')
      // Split out the top-level domain (TLD)
      | extend tld = parts[(array_length(parts)-1)]
      // Validate parsed domain by checking if the TLD is in the list of TLDs in our threat feed
      | where tld in~ (list_tlds)
      | extend Syslog_TimeGenerated = TimeGenerated
    ) on $left.TI_DomainEntity==$right.domain
    | where Syslog_TimeGenerated < ExpirationDateTime
    // Retrieve the latest syslog timestamp for each indicator and domain combination
    | summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain
    // Select the desired columns for the final result set
    | project Syslog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SyslogMessage, Computer, ProcessName, domain, HostIP, Url, Type, TI_DomainEntity
    // Extract the hostname from the Computer field
    | extend HostName = tostring(split(Computer, '.', 0)[0])
    // Extract the DNS domain from the Computer field
    | extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
    // Assign the Syslog_TimeGenerated value to the timestamp field
    | extend timestamp = Syslog_TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: HostIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.4.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let Domain_Indicators is inlined into the numbered stages below.

Let binding: list_tlds used in Stage 7

let list_tlds = ThreatIntelligenceIndicator
  | where isnotempty(DomainName)
  | where TimeGenerated > ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  | extend parts = split(DomainName, '.')
  | extend tld = parts[(array_length(parts)-1)]
  | summarize count() by tostring(tld)
  | summarize make_list(tld);

Stages 1 to 6 define let Domain_Indicators (the rule's main pipeline source); stages 7 to 11 run on it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(DomainName)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 5: where

| where Active == true and ExpirationDateTime > now()

Stage 6: extend

| extend TI_DomainEntity = DomainName

Stage 7: join

Domain_Indicators
| join kind=innerunique (
    Syslog
    | where TimeGenerated > ago(dt_lookBack)
    | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
    | where isnotempty(domain)
    | extend parts = split(domain, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | where tld in~ (list_tlds)
    | extend Syslog_TimeGenerated = TimeGenerated
  ) on $left.TI_DomainEntity==$right.domain

Stage 8: where

| where Syslog_TimeGenerated < ExpirationDateTime

Stage 9: summarize

| summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain

Stage 10: project

| project Syslog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SyslogMessage, Computer, ProcessName, domain, HostIP, Url, Type, TI_DomainEntity

Stage 11: extend (3 consecutive steps)

| extend HostName = tostring(split(Computer, '.', 0)[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
| extend timestamp = Syslog_TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Computerproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
HostIPproject
IndicatorIdproject
ProcessNameproject
SyslogMessageproject
Syslog_TimeGeneratedproject
TI_DomainEntityproject
ThreatTypeproject
Typeproject
Urlproject
domainproject
HostNameextend
DnsDomainextend
timestampextend