Detection rules › Kusto
A client made a web request to a potentially harmful file (ASIM Web Session schema)
'This rule identifies a web request to a URL that holds a file type, including .ps1, .bat, .vbs, and .scr that can be harmful if downloaded. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1189 Drive-by Compromise |
Rule body kusto
id: 09c49590-4e9d-4da9-a34d-17222d0c9e7e
name: A client made a web request to a potentially harmful file (ASIM Web Session schema)
description: |
'This rule identifies a web request to a URL that holds a file type, including .ps1, .bat, .vbs, and .scr that can be harmful if downloaded. This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
severity: Medium
tags:
- ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/CiscoUmbrella/CiscoUmbrellaRequestBlocklistedFileType.yaml
version: 1.0.0
- Schema: ASimWebSession
SchemaVersion: 0.2.1
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
let default_file_ext_blocklist = dynamic(['.ps1', '.vbs', '.bat', '.scr']); // Update this list as per your requirement
let custom_file_ext_blocklist=toscalar(_GetWatchlist('RiskyFileTypes')
| extend Extension=column_ifexists("Extension", "")
| where isnotempty(Extension)
| summarize make_set(Extension)); // If you have an extensive list, you can also create a Watchlist that includes the file extensions you want to detect
let file_ext_blocklist = array_concat(default_file_ext_blocklist, custom_file_ext_blocklist);
_Im_WebSession(starttime=ago(10min), url_has_any=file_ext_blocklist, eventresult='Success')
| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
| extend requestedFileExtension=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))
| where requestedFileExtension in (file_ext_blocklist)
| summarize
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated),
EventCount=count()
by SrcIpAddr, SrcUsername, SrcHostname, requestedFileName, Url
| extend
Name = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 0)[0]), SrcUsername),
UPNSuffix = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 1)[0]), "")
alertDetailsOverride:
alertDisplayNameFormat: Client {{SrcIpAddr}} accessed a URL with potentially harmful extension {{requestedFileExtension}}
alertDescriptionFormat: The client at address {{SrcIpAddr}} accessed the URL {{Url}} that has the extension {{requestedFileExtension}}. Downloading a file with this extension may be harmful and may indicate malicious activity.
customDetails:
requestedFileExt: requestedFileExtension
Username: SrcUsername
SrcHostname: SrcHostname
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: File
fieldMappings:
- identifier: Name
columnName: requestedFileName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUsername
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
version: 1.1.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Yaron
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let default_file_ext_blocklist = dynamic(['.ps1', '.vbs', '.bat', '.scr']);
let file_ext_blocklist = array_concat(default_file_ext_blocklist, custom_file_ext_blocklist);
Let binding: custom_file_ext_blocklist
let custom_file_ext_blocklist = toscalar(_GetWatchlist('RiskyFileTypes')
| extend Extension=column_ifexists("Extension", "")
| where isnotempty(Extension)
| summarize make_set(Extension));
Stage 1: source
_Im_WebSession(starttime=ago(10min), url_has_any=file_ext_blocklist, eventresult='Success')
Stage 2: extend
| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
Stage 3: extend
| extend requestedFileExtension=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))
Stage 4: where
| where requestedFileExtension in (file_ext_blocklist)
Stage 5: summarize
| summarize
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated),
EventCount=count()
by SrcIpAddr, SrcUsername, SrcHostname, requestedFileName, Url
Stage 6: 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 |
|---|---|---|
requestedFileExtension | 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 |
|---|---|
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
requestedFileName | summarize |
Name | extend |
UPNSuffix | extend |