↳ GitHub sourceAnalytics ruleMedium

A potentially malicious web request was executed against a web server

Description

'Detects unobstructed Web Application Firewall (WAF) activity in sessions where the WAF blocked incoming requests by computing the ratio between blocked requests and unobstructed WAF requests in these sessions (BlockvsSuccessRatio metric). A high ratio value for a given client IP and hostname calls for further investigation of the WAF data in that session, due to the significantly high number of blocked requests and a few unobstructed logs that may be malicious but have passed undetected through the WAF. The successCode variable defines what the detection thinks is a successful status code and should be altered to fit the environment.'
Rule type
Scheduled
Version
1.0.6
Declared status
Available
Query frequency
1d
Query period
1d
Trigger
gt 0

Declared MITRE coverage

Declared sources

Metadata from the source file. No dependencies inferred from KQL.

Connectors

Data types

KQL query

Original query, unchanged.

let queryperiod = 1d;
let mode = dynamic(['Blocked', 'Detected']);
let successCode = dynamic(['200', '101','204', '400','504','304','401','500']);
let sessionBin = 30m;
AGWFirewallLogs
| where TimeGenerated > ago(queryperiod)
| where Action  in (mode)
| sort by Hostname asc, ClientIp asc, TimeGenerated asc
| extend SessionBlockedStarted = row_window_session(TimeGenerated, queryperiod, 10m, ((ClientIp != prev(ClientIp)) or (Hostname != prev(Hostname))))
| summarize SessionBlockedEnded = max(TimeGenerated), SessionBlockedCount = count() by Hostname, ClientIp, SessionBlockedStarted
| extend TimeKey = range(bin(SessionBlockedStarted, sessionBin), bin(SessionBlockedEnded, sessionBin), sessionBin)
| mv-expand TimeKey to typeof(datetime)
| join kind = inner(
    AGWAccessLogs
    | where TimeGenerated > ago(queryperiod)
    | where  (isempty(HttpStatus) or HttpStatus in (successCode))
    | extend TimeKey = bin(TimeGenerated, sessionBin)
    | extend Hostname = coalesce(Host,OriginalHost)
    | extend ClientIp = tostring(ClientIp)
) on TimeKey, Hostname, ClientIp
| where TimeGenerated between (SessionBlockedStarted..SessionBlockedEnded)
| extend
    OriginalRequestUriWithArgs = column_ifexists("OriginalRequestUriWithArgs", ""),
    ServerStatus = column_ifexists("ServerStatus", "")
| summarize
    SuccessfulAccessCount = count(),
    UserAgents = make_set(UserAgent, 250),
    RequestURIs = make_set(RequestUri, 250),
    OriginalRequestURIs = make_set(OriginalRequestUriWithArgs, 250),
    SuccessCodes = make_set(HttpStatus, 250),
    SuccessCodes_BackendServer = make_set(ServerStatus, 250),
    take_any(SessionBlockedEnded, SessionBlockedCount)
    by Hostname, ClientIp, SessionBlockedStarted 
| where SessionBlockedCount > SuccessfulAccessCount
| extend BlockvsSuccessRatio = SessionBlockedCount/toreal(SuccessfulAccessCount)
| sort by BlockvsSuccessRatio desc, SessionBlockedStarted asc
| project-reorder SessionBlockedStarted, SessionBlockedEnded, Hostname, ClientIp, SessionBlockedCount, SuccessfulAccessCount, BlockvsSuccessRatio, SuccessCodes, RequestURIs, OriginalRequestURIs, UserAgents

Declared entities

IP

Related content

Links established from declared identifiers and solution manifests.

Source provenance

GitHub

Displayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.

Source identifier
46ac55ae-47b8-414a-8f94-89ccd1962178
Additional source files 3Solutions/Azure Web Application Firewall (WAF)/Analytic Rules/MaliciousWAFSessions.yamlsource ↗Detections/AzureDiagnostics/MaliciousWAFSessions.yamlmigration-note ↗Solutions/Azure Web Application Firewall (WAF)/Data/Solution_AzureWAF.jsonsolution-membership ↗
GSTEP / CATALOG TRACKING

Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC

GSTEP sync dates, separate from the source content’s publication dates.