From cb142a9862b1d48f18e99e5db0834a1380bf5eac Mon Sep 17 00:00:00 2001 From: shabaz-github <104413086+shabaz-github@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:26:04 +0530 Subject: [PATCH] Add detection for CAPTCHA bot traffic in WAF logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This detection identifies IPs served CAPTCHA challenges by Azure Front Door WAF, grouping events and raising findings for IPs with ≥ 3 challenges. --- ... Based on Captcha Bot Traffic Thresholds.kql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Azure WAF/Playbook - Sentinel additional detections/AFD-WAF-Detection Based on Captcha Bot Traffic Thresholds.kql diff --git a/Azure WAF/Playbook - Sentinel additional detections/AFD-WAF-Detection Based on Captcha Bot Traffic Thresholds.kql b/Azure WAF/Playbook - Sentinel additional detections/AFD-WAF-Detection Based on Captcha Bot Traffic Thresholds.kql new file mode 100644 index 0000000..8f9088f --- /dev/null +++ b/Azure WAF/Playbook - Sentinel additional detections/AFD-WAF-Detection Based on Captcha Bot Traffic Thresholds.kql @@ -0,0 +1,17 @@ +//This detection identifies source IPs repeatedly served a CAPTCHA challenge by Azure Front Door WAF within the selected lookback window (default 90 days). It groups Front Door WAF events by socketIP_s (labeled as SourceIp) and raises findings for any IP that hit the CAPTCHA action ≥ 3 times. For each flagged IP, it provides the first/last seen times, total challenge count, and unique URIs the IP requested. This helps surface persistent or recurring bot/automated traffic that was challenged by the WAF, so analysts can decide whether to block, rate‑limit, or tune bot protections. + +let lookback = 90d; +let minChallenges = 3; +AzureDiagnostics +| where Category =~ "FrontDoorWebApplicationFirewallLog" +| where TimeGenerated > ago(lookback) +| where (action_s in~ ("Captcha") + or tostring(details_msg_s) contains "captcha") +| project TimeGenerated, clientIp_s, requestUri_s, socketIP_s +| summarize Count = count(), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated), + URIs = make_set(requestUri_s) + by SourceIp=socketIP_s +| where Count >= minChallenges +| order by Count desc