KQL Query Library for Azure WAF Workshop¶
This document contains reusable KQL (Kusto Query Language) queries for analyzing WAF logs in Log Analytics. Used across Labs 3, 5, 7, 8, and 10.
Table of Contents¶
- Application Gateway WAF Logs
- Front Door WAF Logs
- False Positive Analysis
- Bot Traffic Analysis
- Rate Limiting Analysis
- Geographic Analysis
- Dashboards & Summaries
Application Gateway WAF Logs¶
All WAF events (last 1 hour)¶
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK"
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(1h)
| project TimeGenerated, clientIp_s, requestUri_s, ruleSetType_s, ruleId_s,
message_s, action_s, hostname_s, details_message_s
| order by TimeGenerated desc
Blocked requests only¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| where TimeGenerated > ago(1h)
| project TimeGenerated, clientIp_s, requestUri_s, ruleId_s, message_s, action_s
| order by TimeGenerated desc
Top triggered rules¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(24h)
| summarize Count = count() by ruleId_s, message_s, action_s
| order by Count desc
| take 20
Top attacking IPs¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s in ("Blocked", "Matched")
| where TimeGenerated > ago(24h)
| summarize AttackCount = count(),
DistinctRules = dcount(ruleId_s),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by clientIp_s
| order by AttackCount desc
| take 20
WAF events timeline (chart)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(24h)
| summarize Count = count() by bin(TimeGenerated, 5m), action_s
| render timechart
Anomaly score distribution (DRS 2.1)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleSetType_s == "Microsoft_DefaultRuleSet"
| where TimeGenerated > ago(24h)
| extend AnomalyScore = toint(details_data_s)
| where isnotnull(AnomalyScore) and AnomalyScore > 0
| summarize Count = count() by AnomalyScore
| order by AnomalyScore asc
| render columnchart
Front Door WAF Logs¶
All WAF events (last 1 hour)¶
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.CDN"
| where Category == "FrontDoorWebApplicationFirewallLog"
| where TimeGenerated > ago(1h)
| project TimeGenerated, clientIP_s, requestUri_s, ruleName_s,
action_s, policy_s, trackingReference_s, host_s
| order by TimeGenerated desc
Blocked requests by rule¶
AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where action_s == "Block"
| where TimeGenerated > ago(24h)
| summarize Count = count() by ruleName_s, action_s
| order by Count desc
Front Door WAF timeline¶
AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where TimeGenerated > ago(24h)
| summarize Count = count() by bin(TimeGenerated, 5m), action_s
| render timechart
False Positive Analysis¶
Find potential false positives (legitimate traffic blocked)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked" or action_s == "Matched"
| where TimeGenerated > ago(24h)
| summarize
HitCount = count(),
SampleURIs = make_set(requestUri_s, 5),
SampleIPs = make_set(clientIp_s, 5)
by ruleId_s, message_s
| order by HitCount desc
Analyze specific rule hits in detail¶
// Replace RULE_ID with the rule you want to investigate
let targetRule = "942130"; // Example: SQL injection rule
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleId_s == targetRule
| where TimeGenerated > ago(24h)
| project TimeGenerated, clientIp_s, requestUri_s,
details_message_s, details_data_s, hostname_s
| order by TimeGenerated desc
| take 50
Compare Detection vs. Prevention mode results¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(24h)
| summarize
Detected = countif(action_s == "Detected"),
Blocked = countif(action_s == "Blocked"),
Matched = countif(action_s == "Matched"),
Allowed = countif(action_s == "Allowed")
| extend TotalEvents = Detected + Blocked + Matched + Allowed
Bot Traffic Analysis¶
Bot categorization overview¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleSetType_s == "Microsoft_BotManagerRuleSet"
| where TimeGenerated > ago(24h)
| summarize Count = count() by ruleGroup_s, action_s
| order by Count desc
Bot requests by User-Agent¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleSetType_s == "Microsoft_BotManagerRuleSet"
| where TimeGenerated > ago(24h)
| extend UserAgent = column_ifexists("userAgent_s", "N/A")
| summarize Count = count() by UserAgent, ruleGroup_s, action_s
| order by Count desc
| take 30
JavaScript Challenge results (Front Door)¶
AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where action_s == "JSChallenge"
| where TimeGenerated > ago(24h)
| summarize Count = count() by clientIP_s, ruleName_s
| order by Count desc
Rate Limiting Analysis¶
Rate limit triggers¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleSetType_s == "Custom"
| where message_s contains "rate"
| where TimeGenerated > ago(1h)
| summarize Count = count() by bin(TimeGenerated, 1m), clientIp_s, action_s
| order by TimeGenerated desc
Requests per IP per minute (identify candidates for rate limiting)¶
AzureDiagnostics
| where Category == "ApplicationGatewayAccessLog"
| where TimeGenerated > ago(1h)
| summarize RequestCount = count() by bin(TimeGenerated, 1m), clientIP_s
| where RequestCount > 50
| order by RequestCount desc
Rate limiting with XFF analysis¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(1h)
| extend XFF = column_ifexists("clientIp_s", "N/A")
| summarize
TotalRequests = count(),
BlockedRequests = countif(action_s == "Blocked")
by bin(TimeGenerated, 1m), XFF
| where TotalRequests > 30
| order by TotalRequests desc
Geographic Analysis¶
Requests by country¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(24h)
| extend Country = column_ifexists("clientCountry_s", "Unknown")
| summarize Count = count() by Country, action_s
| order by Count desc
Blocked requests by country (map visualization)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| where TimeGenerated > ago(24h)
| extend Country = column_ifexists("clientCountry_s", "Unknown")
| summarize BlockedCount = count() by Country
| order by BlockedCount desc
| render piechart
Dashboards & Summaries¶
WAF health dashboard (last 24h)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(24h)
| summarize
TotalEvents = count(),
BlockedRequests = countif(action_s == "Blocked"),
DetectedThreats = countif(action_s == "Detected" or action_s == "Matched"),
UniqueAttackers = dcount(clientIp_s),
UniqueRulesTriggered = dcount(ruleId_s),
TopAttackType = arg_max(count(), ruleId_s)
Hourly trend (last 7 days)¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(7d)
| summarize
Total = count(),
Blocked = countif(action_s == "Blocked"),
Detected = countif(action_s == "Detected" or action_s == "Matched")
by bin(TimeGenerated, 1h)
| render timechart