Detection rules › Kusto

Detect web requests to potentially harmful files (ASIM Web Session)

Status
available
Severity
medium
Time window
5m
Group by
DstIpAddr, DstPortNumber, SrcHostname, SrcIpAddr, SrcUsername, Url, requestedFileName
Source
github.com/Azure/Azure-Sentinel

'This rule detects web requests made to URLs containing file types such as .ps1, .bat, .vbs,.scr etc. which have the potential to be harmful if downloaded. This rule uses the Advanced Security Information Model (ASIM) and supports any web session source that complies with ASIM.'

MITRE ATT&CK coverage

Rule body kusto

id: c6608467-3678-45fe-b038-b590ce6d00fb
name: Detect web requests to potentially harmful files (ASIM Web Session)
description: |
  'This rule detects web requests made to URLs containing file types such as .ps1, .bat, .vbs,.scr etc. which have the potential to be harmful if downloaded. This rule uses the [Advanced Security Information Model (ASIM)](https://aka.ms/AboutASIM) and supports any web session source that complies with ASIM.'
severity: Medium
status: Available 
tags:
  - Schema: WebSession
    SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
  - Execution
relevantTechniques:
  - T1133
  - T1203
  - T1566
query: |
  let lookback = 5m;
  let RiskyFileExtensions = materialize(externaldata(Extensions: string)
      [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/RiskyFileExtensionsInUrl.csv"]
      with(format="csv", ignoreFirstRecord=True));
  let CustomRiskyFileExtensions = (_ASIM_GetWatchlistRaw("Web_RiskyFileExtensions") // Create new Watchlist and add your custom indicators(Optional)
      | extend
          Extensions = tostring(WatchlistItem["Extensions"])
      | project Extensions
      | where isnotempty(Extensions));
  let CombinedRiskyFileExtensions = union RiskyFileExtensions, CustomRiskyFileExtensions;
  let knownRiskyFileExtensions=toscalar(CombinedRiskyFileExtensions
      | where isnotempty(Extensions)
      | summarize make_set(Extensions, 1000));
  let Whitelisted_Domains = materialize(externaldata(WhiteListedDomains: string)
      [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/WhiteListedDomainsForWebSessionUseCases.csv"]
      with(format="csv", ignoreFirstRecord=True));
  let CustomWhiteListedDomains = (_ASIM_GetWatchlistRaw("Web_WhiteListedDomains") // Create new Watchlist and add your white listed domains(Optional)
      | extend
          WhiteListedDomains = tostring(WatchlistItem["WhiteListedDomains"])
      | project WhiteListedDomains
      | where isnotempty(WhiteListedDomains));
  let CombinedWhitelisted_Domains = union Whitelisted_Domains, CustomWhiteListedDomains;
  let knownWhitelisted_Domains=toscalar(CombinedWhitelisted_Domains
      | where isnotempty(WhiteListedDomains)
      | summarize make_set(WhiteListedDomains, 1000));
  _Im_WebSession (starttime=ago(lookback), url_has_any=knownRiskyFileExtensions, eventresult='Success')
  | project
      Url,
      SrcIpAddr,
      TimeGenerated,
      SrcUsername,
      SrcHostname,
      DstIpAddr,
      DstPortNumber
  | extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
  | extend requestedFileExt=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))
  | where requestedFileExt in~ (knownRiskyFileExtensions)
  | summarize
      EventCount=count(),
      EventStartTime=min(TimeGenerated),
      EventEndTime=max(TimeGenerated)
      by
      SrcUsername,
      SrcIpAddr,
      SrcHostname,
      DstIpAddr,
      DstPortNumber,
      Url,
      requestedFileName
  | extend FQDN = split(parse_url(Url)["Host"], '.')
  | extend Domain = iif(array_length(FQDN) > 1, strcat(FQDN[-2], '.', FQDN[-1]), FQDN)
  | where Domain !in~ (knownWhitelisted_Domains)
  | project-away FQDN
  | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: requestedFileName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcHostname
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  EventCount: EventCount
  EventStartTime: EventStartTime
  EventEndTime: EventEndTime
  DstIpAddr: DstIpAddr
alertDetailsOverride:
  alertDisplayNameFormat: "User '{{SrcUsername}}' with IP address '{{SrcIpAddr}}' accessed a potentially harmful URL"
  alertDescriptionFormat: "User accessed URL - '{{Url}}' that contains a file - '{{requestedFileName}}' with risky extension. Downloading this file could pose a potential risk"
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 5m;
let CombinedRiskyFileExtensions = union RiskyFileExtensions, CustomRiskyFileExtensions;
let CombinedWhitelisted_Domains = union Whitelisted_Domains, CustomWhiteListedDomains;

Let binding: RiskyFileExtensions

let RiskyFileExtensions = materialize(externaldata(Extensions: string)
    [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/RiskyFileExtensionsInUrl.csv"]
    with(format="csv", ignoreFirstRecord=True));

Let binding: CustomRiskyFileExtensions

let CustomRiskyFileExtensions = (_ASIM_GetWatchlistRaw("Web_RiskyFileExtensions")
    | extend
        Extensions = tostring(WatchlistItem["Extensions"])
    | project Extensions
    | where isnotempty(Extensions));

Let binding: knownRiskyFileExtensions

let knownRiskyFileExtensions = toscalar(CombinedRiskyFileExtensions
    | where isnotempty(Extensions)
    | summarize make_set(Extensions, 1000));

Derived from CombinedRiskyFileExtensions.

Let binding: Whitelisted_Domains

let Whitelisted_Domains = materialize(externaldata(WhiteListedDomains: string)
    [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/WhiteListedDomainsForWebSessionUseCases.csv"]
    with(format="csv", ignoreFirstRecord=True));

Let binding: CustomWhiteListedDomains

let CustomWhiteListedDomains = (_ASIM_GetWatchlistRaw("Web_WhiteListedDomains")
    | extend
        WhiteListedDomains = tostring(WatchlistItem["WhiteListedDomains"])
    | project WhiteListedDomains
    | where isnotempty(WhiteListedDomains));

Let binding: knownWhitelisted_Domains

let knownWhitelisted_Domains = toscalar(CombinedWhitelisted_Domains
    | where isnotempty(WhiteListedDomains)
    | summarize make_set(WhiteListedDomains, 1000));

Derived from CombinedWhitelisted_Domains.

Stage 1: source

_Im_WebSession (starttime=ago(lookback), url_has_any=knownRiskyFileExtensions, eventresult='Success')

Stage 2: project

| project
    Url,
    SrcIpAddr,
    TimeGenerated,
    SrcUsername,
    SrcHostname,
    DstIpAddr,
    DstPortNumber

Stage 3: extend

| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])

Stage 4: extend

| extend requestedFileExt=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))

Stage 5: where

| where requestedFileExt in~ (knownRiskyFileExtensions)

References knownRiskyFileExtensions (defined above).

Stage 6: summarize

| summarize
    EventCount=count(),
    EventStartTime=min(TimeGenerated),
    EventEndTime=max(TimeGenerated)
    by
    SrcUsername,
    SrcIpAddr,
    SrcHostname,
    DstIpAddr,
    DstPortNumber,
    Url,
    requestedFileName

Stage 7: extend

| extend FQDN = split(parse_url(Url)["Host"], '.')

Stage 8: extend

| extend Domain = iif(array_length(FQDN) > 1, strcat(FQDN[-2], '.', FQDN[-1]), FQDN)

Stage 9: where

| where Domain !in~ (knownWhitelisted_Domains)

References knownWhitelisted_Domains (defined above).

Stage 10: project-away

| project-away FQDN

Stage 11: extend

| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
DomaineqknownWhitelisted_Domains

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
requestedFileExtin
  • knownRiskyFileExtensions

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
DstIpAddrsummarize
DstPortNumbersummarize
EventCountsummarize
EventEndTimesummarize
EventStartTimesummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
Urlsummarize
requestedFileNamesummarize
Domainextend
Nameextend
UPNSuffixextend