A WAF you set and forget is a WAF that doesn't protect.
Web Application Firewalls are marketed as turnkey security solutions. Enable it, and you're protected. Reality is different. We've assessed organizations spending $50,000+/year on WAF services that were essentially running in "log only" mode - detecting attacks but blocking nothing.
This guide covers the five most common WAF misconfigurations we see, why they happen, and exactly how to fix them.
The WAF Illusion
WAFs fail not because the technology is bad, but because:
- Fear of breaking things: Teams leave WAFs in "detect" mode indefinitely
- Complexity avoidance: Default rules are enabled, custom rules are skipped
- Set and forget: Initial config is never revisited
- Alert fatigue: Too many false positives lead to ignored alerts
- Vendor trust: "The vendor handles it" assumption
Let's fix each of these.
Mistake #1: Running in "Log Only" or "Simulate" Mode
The most common and most dangerous misconfiguration. Your WAF is watching attacks happen and writing them to a log file. It's not blocking anything.
Why it happens: Teams enable WAFs in detect-only mode "temporarily" during rollout to avoid breaking legitimate traffic. That temporary state becomes permanent.
The risk: Attackers face zero resistance. Your logs show attacks, but your application absorbs them all.
The Fix: Graduate to Block Mode with Staged Rollout
Don't flip from "log" to "block" overnight. Use a staged approach:
# Example: Cloudflare WAF staged rollout
# Week 1: Simulate mode, monitor logs
# Week 2: Block mode for high-confidence rules only
# Week 3: Block mode for medium-confidence rules
# Week 4: Block mode for all rules, with exceptions
# AWS WAF example - change from COUNT to BLOCK
{
"Name": "SQLInjectionRule",
"Priority": 1,
"Action": {
"Block": {} # Changed from "Count": {}
},
"Statement": {
"SqliMatchStatement": {
"FieldToMatch": { "Body": {} },
"TextTransformations": [
{ "Priority": 0, "Type": "URL_DECODE" }
]
}
}
}
Verification: After enabling block mode, check for increased 403 responses. If legitimate users complain, add targeted exceptions - don't revert to log mode.
Mistake #2: Overly Broad Bypass Rules
To stop false positives, someone added a bypass rule. That rule is now a highway for attackers.
Common examples:
- Bypass WAF for all requests from "internal" IP ranges (including AWS metadata IPs)
- Bypass WAF for requests containing a specific header (trivially spoofable)
- Bypass WAF for entire URL paths like
/api/* - Bypass WAF for requests from "trusted" user agents
The Fix: Narrow and Audit Bypass Rules
# BAD: Overly broad bypass
{
"condition": "path starts with /api/",
"action": "allow" # Bypasses ALL WAF rules for API
}
# GOOD: Targeted exception for specific false positive
{
"condition": "path equals /api/v1/upload AND method equals POST",
"exception": ["rule-942100"], # Only bypasses one specific rule
"reason": "File uploads trigger SQLi false positive - JIRA-1234"
}
Audit checklist:
- List all bypass/exception rules
- Document why each exists (with ticket reference)
- Verify each is as narrow as possible
- Set calendar reminder to review quarterly
Mistake #3: No Rate Limiting (or Limits Set Too High)
WAF without rate limiting is like a bouncer who checks IDs but lets unlimited people through the door.
What we see:
- Rate limits set at 10,000 requests/minute when normal traffic is 100/minute
- Rate limits only on
/loginwhile/api/searchhas no limit - Rate limits based on IP only (easily bypassed with rotating IPs)
- No rate limiting at all - WAF only does pattern matching
The Fix: Multi-Layer Rate Limiting
# Cloudflare rate limiting example
# Layer 1: Global rate limit
rate_limit:
path: "/*"
threshold: 1000/minute
action: challenge
# Layer 2: Sensitive endpoint limits
rate_limit:
path: "/api/login"
threshold: 5/minute
action: block
rate_limit:
path: "/api/search"
threshold: 30/minute
action: block
# Layer 3: Per-user limits (if authenticated)
rate_limit:
path: "/api/*"
key: "header:Authorization"
threshold: 100/minute
action: block
Setting the right threshold:
- Analyze normal traffic patterns for 2 weeks
- Find 99th percentile request rate per IP
- Set limit at 2-3x that value
- Monitor and adjust based on blocked requests
Mistake #4: Missing Request Body Inspection
Your WAF inspects URLs and headers. Attackers put payloads in POST bodies.
Why it happens:
- Body inspection is disabled by default (performance reasons)
- Body size limits are too small (8KB default, but uploads are larger)
- JSON/XML parsing is disabled
- Multipart form data is not inspected
The Fix: Enable Full Request Inspection
# AWS WAF - Enable body inspection with proper limits
{
"SizeConstraintStatement": {
"FieldToMatch": {
"Body": {
"OversizeHandling": "CONTINUE" # Don't skip large bodies
}
},
"ComparisonOperator": "LE",
"Size": 65536, # 64KB body inspection
"TextTransformations": [
{ "Priority": 0, "Type": "NONE" }
]
}
}
# ModSecurity - Enable request body access
SecRequestBodyAccess On
SecRequestBodyLimit 13107200 # 12.5MB limit
SecRequestBodyNoFilesLimit 131072 # 128KB for non-file content
SecRequestBodyLimitAction Reject
Performance Consideration
Body inspection adds latency. For high-traffic APIs, consider inspecting bodies only for sensitive endpoints, or use async inspection that doesn't block requests.
Mistake #5: Stale Rule Sets and No Custom Rules
Default OWASP rules are enabled. Nothing else. Rules haven't been updated since deployment.
Problems:
- Default rules don't know your application's specific vulnerabilities
- New attack patterns emerge; old rules don't catch them
- Your business logic vulnerabilities aren't covered
- API-specific attacks require custom rules
The Fix: Layer Custom Rules on Updated Defaults
# Custom rule examples
# Block requests with suspicious parameter patterns
{
"name": "block-mass-assignment",
"condition": "request.body contains 'isAdmin' OR
request.body contains 'role=' OR
request.body contains 'permissions'",
"action": "block",
"endpoints": ["/api/users/*", "/api/profile"]
}
# Block known bad patterns specific to your stack
{
"name": "block-graphql-introspection",
"condition": "request.body contains '__schema' OR
request.body contains '__type'",
"action": "block",
"endpoints": ["/graphql"]
}
# Rate limit password reset (business logic protection)
{
"name": "rate-limit-password-reset",
"condition": "path equals /api/password/reset",
"rate_limit": "3 per hour per IP",
"action": "block"
}
Rule update schedule:
| Rule Type | Update Frequency | Source |
|---|---|---|
| OWASP Core Rules | Monthly | OWASP CRS GitHub |
| Vendor-managed rules | Automatic | Vendor updates |
| Custom rules | After each pentest/incident | Internal security team |
| IP reputation lists | Daily | Threat intelligence feeds |
WAF Validation Checklist
Use this checklist to audit your WAF configuration:
| Check | Expected | How to Verify |
|---|---|---|
| Mode is "Block" | Yes | Check WAF dashboard/config |
| Bypass rules documented | All have JIRA/ticket refs | Review exception list |
| Rate limits configured | All endpoints have limits | Test with load generator |
| Body inspection enabled | Yes, with appropriate size | Send POST with SQLi in body |
| Rules updated recently | Within 30 days | Check rule version/timestamp |
| Custom rules exist | At least 5 app-specific rules | Count custom rule entries |
| Logging captures full requests | Headers, body, response code | Review sample log entries |
| Alerts configured | For high-severity blocks | Trigger test attack, check alerts |
Testing Your WAF
Don't assume your WAF works. Test it.
# Basic WAF testing (on your own systems only!)
# Test 1: SQLi detection
curl "https://yourdomain.com/search?q=1' OR '1'='1"
# Expected: 403 Forbidden
# Test 2: XSS detection
curl "https://yourdomain.com/page?name="
# Expected: 403 Forbidden
# Test 3: Path traversal
curl "https://yourdomain.com/file?path=../../../etc/passwd"
# Expected: 403 Forbidden
# Test 4: Rate limiting
for i in {1..100}; do curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/api/endpoint; done | sort | uniq -c
# Expected: Mix of 200s initially, then 429s or 403s
# Test 5: POST body inspection
curl -X POST -d "username=admin' OR '1'='1" https://yourdomain.com/api/login
# Expected: 403 Forbidden
Automated WAF Testing
Tools like OWASP ZAP, Burp Suite, and Nuclei can automate WAF bypass testing. Run these against staging environments regularly.
Platform-Specific Quick Wins
Cloudflare
- Enable "Managed Ruleset" AND create custom rules
- Set Security Level to at least "Medium"
- Enable Bot Fight Mode
- Configure rate limiting rules (not just DDoS protection)
AWS WAF
- Deploy AWS Managed Rules AND your own rule groups
- Enable logging to S3 or CloudWatch
- Use labels for complex rule logic
- Set appropriate body inspection size limits
Cloudfront + Shield
- Enable AWS Shield Advanced for DDoS protection
- Use origin access control (OAC) to protect origin
- Configure custom error pages (don't leak stack traces)
Akamai
- Enable Kona Site Defender in blocking mode
- Configure network lists for IP reputation
- Set up rate controls per endpoint
When WAF Isn't Enough
WAFs are one layer. They don't replace:
- Secure coding practices: Fix the vulnerability, don't just filter it
- API gateways: For authentication, authorization, rate limiting
- Origin protection: WAF bypass happens when origin is exposed
- DDoS scrubbing: Volumetric attacks need network-level mitigation
Is Your WAF Actually Protecting You?
DDactic assesses WAF configurations and tests for bypasses. Find out if your investment is paying off.
Get a WAF Assessment