Detection rules › Kusto
Detect web requests to potentially harmful files (ASIM Web Session)
'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
| Tactic | Techniques |
|---|---|
| Initial Access | T1133 External Remote Services, T1566 Phishing |
| Execution | T1203 Exploitation for Client Execution |
| Persistence | T1133 External Remote Services |
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.
| Field | Kind | Excluded values |
|---|---|---|
Domain | eq | knownWhitelisted_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.
| Field | Kind | Values |
|---|---|---|
requestedFileExt | 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 |
|---|---|
DstIpAddr | summarize |
DstPortNumber | summarize |
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
requestedFileName | summarize |
Domain | extend |
Name | extend |
UPNSuffix | extend |