Detection rules › Kusto

TI Map Domain entity to Syslog

Severity
medium
Time window
14d
Group by
DomainName, Id, IndicatorId, ObservableValue, 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: cd19434e-10f2-4e2f-b3c1-ce6f08ac5357
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:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
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 = ThreatIntelIndicators
    | where TimeGenerated > ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
    | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
    | where IndicatorType == "domain-name"
    | extend DomainName = tolower(ObservableValue)
    | 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 = ThreatIntelIndicators
   | 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))
    | extend IndicatorId = tostring(split(Id, "--")[2])
    | extend Url = iff(ObservableKey == "url:value", ObservableValue, "");
   // Join the threat intelligence indicators with syslog data on matching domain entities
   Domain_Indicators
   | project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
    | 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.DomainName==$right.domain
    | where Syslog_TimeGenerated < ValidUntil
    // 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
    | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
    | extend Description = tostring(parse_json(Data).description)
    | project Syslog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, SyslogMessage, Computer, ProcessName, domain, HostIP, Type, DomainName, Url
    // 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.7
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 12

let list_tlds = ThreatIntelIndicators
 | where TimeGenerated > ago(ioc_lookBack)
 | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
 | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
 | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
 | where IndicatorType == "domain-name"
 | extend DomainName = tolower(ObservableValue)
 | extend parts = split(DomainName, '.')
 | extend tld = parts[(array_length(parts)-1)]
 | summarize count() by tostring(tld)
 | summarize make_list(tld);

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

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType == "domain-name"

Stage 4: extend

| extend DomainName = tolower(ObservableValue)

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 8: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 9: extend

| extend IndicatorId = tostring(split(Id, "--")[2])

Stage 10: extend

| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
Url =
ifObservableKey == "url:value"ObservableValue
else""

Stage 11: project-reorder

Domain_Indicators
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type

Stage 12: join

| 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.DomainName==$right.domain

Stage 13: where

| where Syslog_TimeGenerated < ValidUntil

Stage 14: summarize

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

Stage 15: extend

| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))

Stage 16: extend

| extend Description = tostring(parse_json(Data).description)

Stage 17: project

| project Syslog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, SyslogMessage, Computer, ProcessName, domain, HostIP, Type, DomainName, Url

Stage 18: 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
Confidenceproject
Descriptionproject
DomainNameproject
HostIPproject
Idproject
ProcessNameproject
SyslogMessageproject
Syslog_TimeGeneratedproject
Typeproject
Urlproject
ValidUntilproject
domainproject
HostNameextend
DnsDomainextend
timestampextend