Contents
- 0.1 1) High volume blocks from a single source (scan or L7 flood)
- 0.2 2) SQL Injection indicators in query string (generic)
- 0.3 3) XSS indicators (generic)
- 0.4 4) Path traversal and sensitive file probing
- 0.5 5) Credential stuffing or brute-force against login endpoints
- 0.6 6) Bot-like user agents and tooling
- 0.7 7) Suspicious method use (TRACE, TRACK) and odd verbs
- 0.8 8) Potential SSRF probing patterns (limited but useful)
- 0.9 9) New source countries (if geo IP is available)
- 1 Vendor-specific examples
- 2 Operational guidance (what to alert on)
Below are Splunk detection query examples for WAF logs. They are written to be vendor-neutral, and I’ve included vendor-specific variants (AWS WAF, Cloudflare) where it materially helps.
Assumptions:
- Your WAF logs land in Splunk with fields resembling
src_ip,uri_path,uri_query,http_method,status,action,rule,user_agent,host,bytes,request_body(where available). - Replace
index=wafandsourcetype=waf:*with your actual index/sourcetype.
1) High volume blocks from a single source (scan or L7 flood)
Detects a single IP triggering lots of blocks across multiple URLs.
index=waf sourcetype=waf:*
(action=block OR status IN (403,406,429))
| stats count as blocks dc(uri_path) as unique_paths values(rule) as rules earliest(_time) as firstSeen latest(_time) as lastSeen by src_ip host
| where blocks >= 50 OR unique_paths >= 20
| eval duration=lastSeen-firstSeen
| sort - blocks
Tuning ideas:
- Increase thresholds during known pentest windows.
- Add
| where cidrmatch("x.x.x.x/yy", src_ip)=0to exclude corporate IP ranges.
2) SQL Injection indicators in query string (generic)
Useful when your WAF vendor does not provide a clean attack_type field.
index=waf sourcetype=waf:*
(action=block OR status IN (403,406))
| eval q=coalesce(uri_query, url_query, query_string, "")
| where match(lower(q), "(union(\s|%20)+select|or(\s|%20)+1=1|'\s*or\s*'1'='1|sleep\(|benchmark\(|information_schema|%27%20or%20)")
| stats count as hits values(uri_path) as paths values(q) as sample_query values(rule) as rules by src_ip host user_agent
| sort - hits
3) XSS indicators (generic)
Looks for script injection patterns in URL/query.
index=waf sourcetype=waf:*
(action=block OR status IN (403,406))
| eval raw=lower(coalesce(uri_query,"")." ".coalesce(uri_path,""))
| where match(raw, "(<script|%3cscript|onerror=|onload=|javascript:|%3cimg|%3csvg)")
| stats count values(uri_path) as paths values(uri_query) as queries by src_ip host user_agent
| sort - count
4) Path traversal and sensitive file probing
Catches ../ traversal and common sensitive targets.
index=waf sourcetype=waf:*
(action=block OR status IN (403,404,406))
| eval target=lower(coalesce(uri_path,""))
| where match(target, "(\.\./|%2e%2e%2f|%252e%252e%252f)")
OR match(target, "(/etc/passwd|/proc/self/environ|/windows/win\.ini|/\.git/|/\.env|/wp-config\.php|/phpmyadmin|/cgi-bin)")
| stats count as hits values(target) as targets values(rule) as rules by src_ip host user_agent
| sort - hits
5) Credential stuffing or brute-force against login endpoints
Focuses on login paths and authentication failures, plus behavioural signals.
index=waf sourcetype=waf:*
| eval path=lower(coalesce(uri_path,""))
| search path IN ("/login","/signin","/wp-login.php","/oauth/token","/api/auth","/users/sign_in")
| stats count as attempts dc(user_agent) as ua_count dc(src_ip) as src_count values(action) as actions values(status) as statuses by host path
| sort - attempts
Variant: “single IP brute forcing a login path”
index=waf sourcetype=waf:*
| eval path=lower(coalesce(uri_path,""))
| search path IN ("/login","/signin","/wp-login.php","/oauth/token","/api/auth","/users/sign_in")
| stats count as attempts dc(user_agent) as ua_count values(status) as statuses values(action) as actions by src_ip host path
| where attempts >= 30
| sort - attempts
6) Bot-like user agents and tooling
Flags typical scanners and automation frameworks.
index=waf sourcetype=waf:*
(action=block OR status IN (403,406,429))
| eval ua=lower(coalesce(user_agent,"unknown"))
| where match(ua, "(sqlmap|nikto|nmap|masscan|acunetix|burp|zap|gobuster|dirbuster|python-requests|curl|wget)")
| stats count values(ua) as user_agents values(uri_path) as paths by src_ip host
| sort - count
7) Suspicious method use (TRACE, TRACK) and odd verbs
TRACE is often disabled; unexpected methods can indicate probing.
index=waf sourcetype=waf:*
| eval m=upper(coalesce(http_method, method, ""))
| where m IN ("TRACE","TRACK","CONNECT") OR (m="PUT" OR m="DELETE")
| stats count values(uri_path) as paths values(status) as statuses values(action) as actions by src_ip host m
| sort - count
Tune it by excluding known APIs that legitimately use PUT/DELETE.
8) Potential SSRF probing patterns (limited but useful)
SSRF can be hard to detect purely at WAF, but these patterns are common.
index=waf sourcetype=waf:*
(action=block OR status IN (403,406))
| eval raw=lower(coalesce(uri_query,"")." ".coalesce(request_body,""))
| where match(raw, "(http://|https://|file://|gopher://|ftp://|127\.0\.0\.1|localhost|169\.254\.169\.254|metadata\.google|metadata\.azure)")
| stats count values(uri_path) as paths values(uri_query) as queries by src_ip host user_agent
| sort - count
9) New source countries (if geo IP is available)
Only works if you have geolocation enrichment (Splunk built-in or via app).
index=waf sourcetype=waf:*
(action=block OR status IN (403,406,429))
| iplocation src_ip
| stats count as blocks dc(uri_path) as unique_paths by Country src_ip host
| where blocks >= 20 AND unique_paths >= 10
| sort - blocks
Use this for “new country with high blocks”, especially for customer-facing apps.
Vendor-specific examples
AWS WAF (common fields: httpRequest.clientIp, terminatingRuleId, action, httpRequest.uri)
index=waf sourcetype=aws:waf
| rename httpRequest.clientIp as src_ip httpRequest.uri as uri_path httpRequest.args as uri_query terminatingRuleId as rule
| search action="BLOCK"
| stats count dc(uri_path) as unique_paths values(rule) as rules by src_ip host
| where count >= 50 OR unique_paths >= 20
| sort - count
Cloudflare WAF (common fields: ClientIP, ClientRequestPath, WAFAction, WAFRuleID)
index=waf sourcetype=cloudflare:waf
| rename ClientIP as src_ip ClientRequestPath as uri_path WAFAction as action WAFRuleID as rule
| search action IN ("block","challenge","jschallenge")
| stats count dc(uri_path) as unique_paths values(rule) as rules by src_ip host
| where count >= 50 OR unique_paths >= 20
| sort - count
Operational guidance (what to alert on)
For alerting, the best high-signal patterns are:
- High blocks per IP with broad path diversity (recon/scanning)
- Login endpoint attack bursts (credential stuffing)
- Tooling user agents (sqlmap, Burp, ZAP, Nikto)
- Traversal and sensitive file probing (
/.env,/.git/,/etc/passwd)
Avoid alert fatigue by:
- Using thresholds
- Suppressing known scanners (internal IPs, authorised testing)
- Deduplicating alerts by
src_ip + host + ruleover a rolling window