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

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 used in Stages 1, 5

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

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 used in Stage 9

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

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)

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)

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

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DomaineqknownWhitelisted_Domainsexcludes:Domain field:"Domain" value:"knownWhitelisted_Domains"

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

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