Detection rules › Kusto

Detect potential presence of a malicious file with a double extension (ASIM Web Session)

Status
available
Severity
medium
Time window
1h
Group by
DstHostname, DstIpAddr, DstPortNumber, FileWithdualextension, SrcHostname, SrcIpAddr, SrcUsername, Url
Source
github.com/Azure/Azure-Sentinel

'Double extension vulnerability is a significant concern in file uploads, as it can lead to various issues if an attacker successfully uploads a virus-infected file.'

MITRE ATT&CK coverage

Rule body kusto

id: 6a71687f-00cf-44d3-93fc-8cbacc7b5615
name: Detect potential presence of a malicious file with a double extension (ASIM Web Session)
description: |
  'Double extension vulnerability is a significant concern in file uploads, as it can lead to various issues if an attacker successfully uploads a virus-infected file.'
severity: Medium
status: Available 
tags:
  - Schema: WebSession
    SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Persistence
  - CommandAndControl
relevantTechniques:
  - T1036
  - T1505
  - T1071
query: |
  let common_file_ext_list = dynamic([".txt", ".xlsx", ".doc", ".docx", ".csv", ".pdf", ".png", ".jpg", ".jpeg"]); // Add list of common files as per your environment
  _Im_WebSession (starttime=ago(1h), eventresult='Success')
  | where HttpRequestMethod in~ ("POST", "PUT") 
  | project
      Url,
      SrcIpAddr,
      SrcUsername,
      SrcHostname,
      DstIpAddr,
      DstPortNumber,
      DstHostname,
      TimeGenerated
  | extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
  | extend FileWithdualextension = extract(@'([\w-]+\.\w+\.\w+)$', 1, requestedFileName, typeof(string))
  | extend SecondExt = tostring(split(FileWithdualextension, '.')[-1])
  | where strcat('.', SecondExt) in~ (common_file_ext_list) // Second extension is mostly from the common files
  | summarize
      EventCount=count(),
      EventStartTime=min(TimeGenerated),
      EventEndTime=max(TimeGenerated)
      by
      SrcIpAddr,
      Url,
      FileWithdualextension,
      SrcUsername,
      SrcHostname,
      DstIpAddr,
      DstPortNumber,
      DstHostname
  | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileWithdualextension
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcHostname
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  EventCount: EventCount
  EventStartTime: EventStartTime
  EventEndTime: EventEndTime
  DstHostname: DstHostname
alertDetailsOverride:
  alertDisplayNameFormat: "User '{{SrcUsername}}' with IP address '{{SrcIpAddr}}' has been observed with posting potentially risky dual extension file"
  alertDescriptionFormat: "User posted file '{{FileWithdualextension}}' which potentially contain dual extensions. This type of activity could be malicious and performed to bypass file upload filters or security measures implemented by the application. Destination server name this request was targetted to - '{{DstHostname}}'"
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let common_file_ext_list = dynamic([".txt", ".xlsx", ".doc", ".docx", ".csv", ".pdf", ".png", ".jpg", ".jpeg"]);

Stage 1: source

_Im_WebSession (starttime=ago(1h), eventresult='Success')

Stage 2: where

| where HttpRequestMethod in~ ("POST", "PUT")

Stage 3: project

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

Stage 4: extend (3 consecutive steps)

| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
| extend FileWithdualextension = extract(@'([\w-]+\.\w+\.\w+)$', 1, requestedFileName, typeof(string))
| extend SecondExt = tostring(split(FileWithdualextension, '.')[-1])

Stage 5: where

| where strcat('.', SecondExt) in~ (common_file_ext_list)

Stage 6: summarize

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

Stage 7: extend

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

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
HttpRequestMethodin
  • POST
  • PUT

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
DstHostnamesummarize
DstIpAddrsummarize
DstPortNumbersummarize
EventCountsummarize
EventEndTimesummarize
EventStartTimesummarize
FileWithdualextensionsummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
Urlsummarize
Nameextend
UPNSuffixextend