Detection rules › Kusto
Detect potential presence of a malicious file with a double extension (ASIM Web Session)
'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
| Tactic | Techniques |
|---|---|
| Persistence | T1505 Server Software Component |
| Stealth | T1036 Masquerading |
| Command & Control | T1071 Application Layer Protocol |
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.
| Field | Kind | Values |
|---|---|---|
HttpRequestMethod | in |
|
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.
| Field | Source |
|---|---|
DstHostname | summarize |
DstIpAddr | summarize |
DstPortNumber | summarize |
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
FileWithdualextension | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
Name | extend |
UPNSuffix | extend |