Detection rules › Kusto
Detect URLs containing known malicious keywords or commands (ASIM Web Session)
'The utilization of system commands or functions in the request URL may suggest that an attacker is trying to gain unauthorized access to the environment by exploiting a vulnerable service.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1133 External Remote Services, T1190 Exploit Public-Facing Application |
| Command & Control | T1071 Application Layer Protocol |
Rule body kusto
id: 32c08696-2e37-4730-86f8-97d9c8b184c9
name: Detect URLs containing known malicious keywords or commands (ASIM Web Session)
description: |
'The utilization of system commands or functions in the request URL may suggest that an attacker is trying to gain unauthorized access to the environment by exploiting a vulnerable service.'
severity: High
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1190
- T1133
- T1071
query: |
let lookback = 5m;
let RiskyCommandsInUrl = materialize(externaldata(Commands: string)
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/CommandsInURL.csv"]
with(format="csv", ignoreFirstRecord=True));
let CustomRiskyCommandsInUrl = (_ASIM_GetWatchlistRaw("Web_RiskyCommandsInUrl") // Create new Watchlist and add your custom indicators(Optional)
| extend
Commands = tostring(WatchlistItem["Commands"])
| project Commands
| where isnotempty(Commands));
let CombinedRiskyCommandsInUrl = union RiskyCommandsInUrl, CustomRiskyCommandsInUrl;
let knownRiskyCommandsInUrl=toscalar(CombinedRiskyCommandsInUrl
| where isnotempty(Commands)
| summarize make_set(Commands, 1000));
// You can add more keywords to the query as necessary, depending on the specific indicators you want to detect.
_Im_WebSession (starttime=ago(lookback), eventresult='Success', url_has_any=knownRiskyCommandsInUrl)
| where isnotempty(Url)
| project Url, SrcIpAddr, SrcUsername, SrcHostname, DstIpAddr, TimeGenerated
| extend Decoded_url = url_decode(Url)
| where Decoded_url has_any (knownRiskyCommandsInUrl)
| summarize
EventCount=count(),
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated)
by SrcIpAddr, SrcUsername, SrcHostname, Url, Decoded_url, DstIpAddr
| 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: IP
fieldMappings:
- identifier: Address
columnName: DstIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUsername
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
EventCount: EventCount
Decoded_url: Decoded_url
EventStartTime: EventStartTime
EventEndTime: EventEndTime
alertDetailsOverride:
alertDisplayNameFormat: "User '{{SrcUsername}}' with IP '{{SrcIpAddr}}' has been identified as making request for URL '{{Url}}' that includes a recognizable malicious command"
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 5m;
let CombinedRiskyCommandsInUrl = union RiskyCommandsInUrl, CustomRiskyCommandsInUrl;
Let binding: RiskyCommandsInUrl
let RiskyCommandsInUrl = materialize(externaldata(Commands: string)
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/CommandsInURL.csv"]
with(format="csv", ignoreFirstRecord=True));
Let binding: CustomRiskyCommandsInUrl
let CustomRiskyCommandsInUrl = (_ASIM_GetWatchlistRaw("Web_RiskyCommandsInUrl")
| extend
Commands = tostring(WatchlistItem["Commands"])
| project Commands
| where isnotempty(Commands));
Let binding: knownRiskyCommandsInUrl
let knownRiskyCommandsInUrl = toscalar(CombinedRiskyCommandsInUrl
| where isnotempty(Commands)
| summarize make_set(Commands, 1000));
Derived from CombinedRiskyCommandsInUrl.
Stage 1: source
_Im_WebSession (starttime=ago(lookback), eventresult='Success', url_has_any=knownRiskyCommandsInUrl)
Stage 2: where
| where isnotempty(Url)
Stage 3: project
| project Url, SrcIpAddr, SrcUsername, SrcHostname, DstIpAddr, TimeGenerated
Stage 4: extend
| extend Decoded_url = url_decode(Url)
Stage 5: where
| where Decoded_url has_any (knownRiskyCommandsInUrl)
References knownRiskyCommandsInUrl (defined above).
Stage 6: summarize
| summarize
EventCount=count(),
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated)
by SrcIpAddr, SrcUsername, SrcHostname, Url, Decoded_url, DstIpAddr
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 |
|---|---|---|
Decoded_url | match |
|
Url | is_not_null |
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 |
|---|---|
Decoded_url | summarize |
DstIpAddr | summarize |
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
Name | extend |
UPNSuffix | extend |