Detection rules › Kusto
Application Gateway WAF - XSS Detection
'Identifies a match for XSS attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement. References: https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1189 Drive-by Compromise |
| Execution | T1203 Exploitation for Client Execution |
| Execution | T0853 Scripting |
Rule body kusto
id: d2bc08fa-030a-4eea-931a-762d27c6a042
name: Application Gateway WAF - XSS Detection
description: |
'Identifies a match for XSS attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement.
References: https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)'
severity: High
requiredDataConnectors:
- connectorId: WAF
dataTypes:
- AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
relevantTechniques:
- T1189
- T1203
- T0853
tags:
- Cross Site Scripting
query: |
let Threshold = 1;
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Matched"
| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "XSS" and toint(TotalInboundScore) >=15 and toint(XSS_Score) >= 10 and toint(SQLI_Score) <= 5) on transactionId_g
| extend Uri = strcat(hostname_s,requestUri_s)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(transactionId_g), Message = make_set(Message), Detail_Message = make_set(details_message_s), Detail_Data = make_set(details_data_s), Total_TransactionId = dcount(transactionId_g) by clientIp_s, Uri, action_s, SQLI_Score, XSS_Score, TotalInboundScore
| where Total_TransactionId >= Threshold
# The Threshold value above can be changed as per your infrastructure's requirement
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Uri
- entityType: IP
fieldMappings:
- identifier: Address
columnName: clientIp_s
version: 1.0.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: shabaz-github
support:
tier: Community
categories:
domains: [ "Security - Threat Protection", "Platform" ]
Stages and Predicates
Parameters
let Threshold = 1;
Stage 1: source
AzureDiagnostics
Stage 2: where
| where Category == "ApplicationGatewayFirewallLog"
Stage 3: where
| where action_s == "Matched"
Stage 4: project
| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
Stage 5: join
| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "XSS" and toint(TotalInboundScore) >=15 and toint(XSS_Score) >= 10 and toint(SQLI_Score) <= 5) on transactionId_g
Stage 6: extend
| extend Uri = strcat(hostname_s,requestUri_s)
Stage 7: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(transactionId_g), Message = make_set(Message), Detail_Message = make_set(details_message_s), Detail_Data = make_set(details_data_s), Total_TransactionId = dcount(transactionId_g) by clientIp_s, Uri, action_s, SQLI_Score, XSS_Score, TotalInboundScore
Stage 8: where
| where Total_TransactionId >= Threshold
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 |
|---|---|---|
Blocked_Reason | contains |
|
Category | eq |
|
SQLI_Score | le |
|
TotalInboundScore | ge |
|
Total_TransactionId | ge |
|
XSS_Score | ge |
|
action_s | eq |
|
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 |
|---|---|
Detail_Data | summarize |
Detail_Message | summarize |
EndTime | summarize |
Message | summarize |
SQLI_Score | summarize |
StartTime | summarize |
TotalInboundScore | summarize |
Total_TransactionId | summarize |
TransactionID | summarize |
Uri | summarize |
XSS_Score | summarize |
action_s | summarize |
clientIp_s | summarize |