Detection rules › Kusto

TI Map URL Entity to UrlClickEvents

Severity
medium
Time window
14d
Group by
Id, ObservableValue, ReportId, TI_Url, Url, UrlChain
Source
github.com/Azure/Azure-Sentinel

This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.

MITRE ATT&CK coverage

TacticTechniques
Command & Control

Telemetry coverage

Rule body

id: ad4fa1f2-2189-459c-9458-f77d2039d2f5
name: TI Map URL Entity to UrlClickEvents
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - UrlClickEvents
  - 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;
  let ioc_lookBack = 14d;
  let UrlClickEvents_ = materialize(UrlClickEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | extend UrlClickEvents_TimeGenerated = TimeGenerated);
  let ChainReportID = UrlClickEvents_
      | mv-expand todynamic(UrlChain)
      | extend UrlChain = tolower(UrlChain)
      | project ReportId, Url, UrlChain;
  // Url is not always in UrlChain, so we need to check both
  let ClickedUrls = 
    (union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
    | distinct Url
    | summarize make_list(Url);
  let TI = materialize(ThreatIntelIndicators
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "url"
  | extend Url = ObservableValue
    | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
    | where TimeGenerated >= ago(ioc_lookBack)
    | where tolower(Url) in (ClickedUrls)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
    | project-rename TI_Url = Url, TI_Type = Type
    );
  (union isfuzzy=false (TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.UrlChain),
    (TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.Url))
  | project-away UrlChain
  | join kind=innerunique (UrlClickEvents_) on ReportId
  | where UrlClickEvents_TimeGenerated < ValidUntil
  | summarize UrlClickEvents_TimeGenerated = arg_max(UrlClickEvents_TimeGenerated, *) by Id
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | project UrlClickEvents_TimeGenerated, AccountUpn, Description, ActivityGroupNames, Id, Type, ValidUntil, Confidence, Url, NetworkMessageId
  | extend timestamp = UrlClickEvents_TimeGenerated
  | extend timestamp = UrlClickEvents_TimeGenerated, Name = tostring(split(AccountUpn, '@', 0)), UPNSuffix = tostring(split(AccountUpn, '@', 1))
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUpn
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.0.5
kind: Scheduled

Stages and Predicates

Stage 0: let

let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let UrlClickEvents_ = materialize(UrlClickEvents
    | where TimeGenerated >= ago(dt_lookBack)
    | extend UrlClickEvents_TimeGenerated = TimeGenerated);
let ChainReportID = UrlClickEvents_
    | mv-expand todynamic(UrlChain)
    | extend UrlChain = tolower(UrlChain)
    | project ReportId, Url, UrlChain;
let ClickedUrls = 
  (union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
  | distinct Url
  | summarize make_list(Url);
let TI = materialize(<inlined as stages below>);

Stage 1: source

let UrlClickEvents_

Stage 2: source

let ChainReportID

Stage 3: source

let ClickedUrls

Stage 4: union

union of 2 branches

Stage 5: source

ThreatIntelIndicators

Stage 6: extend

extend IndicatorType

Stage 7: where

where IndicatorType =~ "url"

Stage 8: extend

extend Url

Stage 9: extend

extend TrafficLightProtocolLevel

Stage 10: where

where TimeGenerated >= ago(1209600s)

Stage 11: where

where Url =~ "ClickedUrls"

Stage 12: summarize

summarize LatestIndicatorTime by Id, ObservableValue

Stage 13: where

where (isempty(ValidUntil) or ValidUntil > now())

Stage 14: project-rename

project-rename

Stage 15: join

join kind=innerunique (ChainReportID) on TI_Url, UrlChain

Stage 16: source

ThreatIntelIndicators

Stage 17: extend

extend IndicatorType

Stage 18: where

where IndicatorType =~ "url"

Stage 19: extend

extend Url

Stage 20: extend

extend TrafficLightProtocolLevel

Stage 21: where

where TimeGenerated >= ago(1209600s)

Stage 22: where

where Url =~ "ClickedUrls"

Stage 23: summarize

summarize LatestIndicatorTime by Id, ObservableValue

Stage 24: where

where (isempty(ValidUntil) or ValidUntil > now())

Stage 25: project-rename

project-rename

Stage 26: join

join kind=innerunique (ChainReportID) on TI_Url, Url

Stage 27: project-away

project-away UrlChain

Stage 28: join

join kind=innerunique (UrlClickEvents_) on ReportId

Stage 29: where

where UrlClickEvents_TimeGenerated < ValidUntil

Stage 30: summarize

summarize UrlClickEvents_TimeGenerated by Id

Stage 31: extend

extend Description

Stage 32: extend

extend ActivityGroupNames

Stage 33: project

project AccountUpn, ActivityGroupNames, Confidence, Description, Id, NetworkMessageId, Type, Url, UrlClickEvents_TimeGenerated, ValidUntil

Stage 34: extend

extend timestamp

Stage 35: extend

extend Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountUpnproject
ActivityGroupNamesproject
Confidenceproject
Descriptionproject
Idproject
NetworkMessageIdproject
Typeproject
Urlproject
UrlClickEvents_TimeGeneratedproject
ValidUntilproject
timestampextend
Nameextend
UPNSuffixextend