A Web Application Firewall (WAF) protects applications from malicious traffic and common web attacks. This guide covers WAF concepts, rules, and deployment.
Understanding WAF
What WAF Protects Against
owasp_top_10:
- "A01:2021 - Broken Access Control"
- "A02:2021 - Cryptographic Failures"
- "A03:2021 - Injection"
- "A04:2021 - Insecure Design"
- "A05:2021 - Security Misconfiguration"
- "A06:2021 - Vulnerable Components"
- "A07:2021 - Auth Failures"
- "A08:2021 - Data Integrity Failures"
- "A09:2021 - Logging Failures"
- "A10:2021 - SSRF"
common_attacks:
- "SQL Injection"
- "Cross-Site Scripting (XSS)"
- "Cross-Site Request Forgery (CSRF)"
- "Path Traversal"
- "Command Injection"
- "LDAP Injection"
- "XML External Entity (XXE)"
WAF Deployment Modes
deployment_modes:
- name: "Block Mode"
description: "Threats are blocked completely"
- name: "Monitor Mode"
description: "Threats are logged but allowed"
- name: "Learning Mode"
description: "Traffic patterns are analyzed to create rules"
WAF Architecture and Placement
A WAF sits between the users of a web app and the application servers. It filters, monitors, and blocks web traffic (HTTP). A good WAF is also bidirectional — it should inspect request traffic on its way to an application server in addition to optionally inspecting response traffic being returned to a client.
Where a WAF Lives
Precisely where the WAF lives is flexible: in a public cloud, in a DMZ near the web servers, or even directly on the web servers themselves.
Client → CDN/WAF (edge) → Load Balancer + WAF → App Servers → Databases
↓ ↓ ↓
Volumetric L7 attacks SQL injection
DDoS absorbed filtered here blocked here
It’s common to see a WAF bundled with a load balancer (reverse proxy). At the point where full Layer 7 load balancing happens, it’s relatively simple to feed the HTTP traffic through a WAF engine and gain its defensive benefits. The load balancing layer can handle TLS termination (feeding only plaintext HTTP into the WAF engine), server load balancing, and high availability.
Defense in Depth
The core principle is that the strongest way to secure a system is to use multiple, independent layers of security — like a multi-layered onion. No single security measure is perfect in the real world. If one or more security measures are bypassed, multiple additional defensive layers remain in place to stop attacks. A WAF is an important defensive layer for web apps exposed to the public internet.
OWASP Core Rule Set (CRS)
The OWASP CRS is the de facto standard set of free and open-source WAF rules, published under the umbrella of the OWASP Foundation. It is used by ModSecurity, Coraza, AWS WAF managed rules, and many commercial WAFs.
Paranoia Levels
CRS rules are spread across four “paranoia level” categories of increasing security:
| Level | Description | False Positives | Recommended Use |
|---|---|---|---|
| PL1 | Simple, uncontroversial rules | Rarely triggered in error | Minimum baseline |
| PL2 | Broader range of detection | Higher likelihood | Default for public web apps |
| PL3 | Even more aggressive rules | High | High-risk environments |
| PL4 | All rules in play, most aggressive | Very high | Banking, e-voting, critical infra |
Recommended default: PL2 for real-world web applications exposed to the public internet. This gives good protection without overwhelming the team with false positives. PL1 may suffice for a “mandated WAF” scenario (compliance-only). PL3 and PL4 are for specialist, high-paranoia deployments that come with ample time and consultancy needed to make them work correctly.
Enabling the CRS
# ModSecurity + OWASP CRS setup
Include /etc/modsecurity/modsecurity.conf
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/rules/*.conf
# Set paranoia level (1-4)
SecAction \
"id:900000, \
phase:1, \
nolog, \
pass, \
t:none, \
setvar:tx.executing_paranoia_level=2"
WAF Rules
Rule Types
rule_types:
- name: "Signature-based"
description: "Match known attack patterns"
- name: "Behavioral"
description: "Detect anomalies in traffic patterns"
- name: "Rate-based"
description: "Limit request frequency"
- name: "Geo-blocking"
description: "Block traffic from specific regions"
- name: "IP Reputation"
description: "Block known malicious IPs"
Custom Rules Example
# AWS WAF custom rules
rules:
- name: "Block SQL Injection"
statement:
sqliMatchStatement:
fieldToMatch:
body:
oversizeHandling: MATCH
samplingPriority: NORMAL
action:
block:
visibilityConfig:
sampledRequestsEnabled: true
- name: "Rate Limit"
statement:
rateBasedStatement:
limit: 1000
aggregateKeyType: IP
action:
block:
- name: "Block XSS"
statement:
xssMatchStatement:
fieldToMatch:
queryString: {}
samplingPriority: NORMAL
action:
block:
ModSecurity Rules
# ModSecurity rules
# Block SQL Injection
SecRule REQUEST_URI|ARGS "@rx (?i)(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|TRUNCATE)\b)" \
"id:1001,phase:1,deny,status:403,msg:'SQL Injection Attempt'"
# Block XSS
SecRule REQUEST_URI|ARGS|REQUEST_HEADERS "@rx (?i)<script[^>]*>.*?</script>" \
"id:1002,phase:1,deny,status:403,msg:'XSS Attempt'"
# Block path traversal
SecRule REQUEST_URI "@rx \.\.(\/|\\)" \
"id:1003,phase:1,deny,status:403,msg:'Path Traversal Attempt'"
# Rate limiting
SecRuleEngine On
SecRequestBodyAccess On
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
Implementation Examples
Cloudflare WAF
# Cloudflare WAF via API
import requests
class CloudflareWAF:
def __init__(self, api_token, zone_id):
self.base_url = "https://api.cloudflare.com/client/v4"
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
self.zone_id = zone_id
def create_firewall_rule(self, name, action, expression):
"""Create WAF rule"""
url = f"{self.base_url}/zones/{self.zone_id}/firewall/rules"
data = {
"filter": {
"expression": expression
},
"action": action,
"priority": 1
}
response = requests.post(url, json=data, headers=self.headers)
return response.json()
def block_ip(self, ip):
"""Block specific IP"""
return self.create_firewall_rule(
name=f"Block {ip}",
action="block",
expression=f"ip.src == {ip}"
)
def allow_bot(self):
"""Allow known bots"""
return self.create_firewall_rule(
name="Allow Good Bots",
action="allow",
expression="cf.client.bot"
)
AWS WAF
# AWS WAF via boto3
import boto3
class AWSWAF:
def __init__(self, region):
self.client = boto3.client('wafv2', region_name=region)
self.scope = "CLOUDFRONT" # or REGIONAL
def create_ip_set(self, name, addresses):
"""Create IP set for blocking/allowing"""
response = self.client.create_ip_set(
Name=name,
Scope=self.scope,
Description=f"IP set for {name}",
Addresses=addresses,
Tags=[{'Key': 'Environment', 'Value': 'Production'}]
)
return response
def create_rule(self, name, statement, action):
"""Create WAF rule"""
response = self.client.create_rule(
Name=name,
Scope=self.scope,
Rules=[{
'Name': name,
'Priority': 0,
'Statement': statement,
'Action': {'Block': {}},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': name
}
}]
)
return response
Managed WAF Comparison (2026)
| Feature | Cloudflare WAF | AWS WAF | Azure WAF | Google Cloud Armor |
|---|---|---|---|---|
| Managed rules | OWASP CRS + Cloudflare managed | AWS managed rules + CRS | CRS (preview) | Preconfigured rules |
| Deployment | CDN edge | CloudFront/ALB | Application Gateway/Front Door | Load balancer/CDN |
| Pricing | $5/mo + $0.50/rule | $5/mo + $1/rule + data | ~$30/mo + traffic | Per request |
| Bot management | Excellent | Basic | Basic | Via reCAPTCHA |
| API protection | Schema validation | API Gateway integration | Front Door | Cloud Armor + Apigee |
| ML/AI detection | Behavioral + threat score | Bot Control | Anomaly scoring | ML-based |
| Rate limiting | Built-in | Rate-based rules | Custom rules | Edge security policies |
| Virtual patching | Yes | Yes | Yes | Yes |
Choosing a Managed WAF
| Scenario | Recommended | Why |
|---|---|---|
| Public web app behind CDN | Cloudflare WAF | Best edge protection, bot management |
| AWS-native stack | AWS WAF | Native integration, managed rule groups |
| Azure enterprise | Azure WAF | Front Door integration, compliance |
| GCP with global load balancing | Cloud Armor | Native GCP, reCAPTCHA integration |
Virtual Patching
Consider a situation where a backend web application has a known vulnerability but cannot be patched. This happens when: the vendor hasn’t released a fix yet, a policy requires waiting for a maintenance window, or the application is legacy and unsupported.
Virtual patches are custom WAF rules that detect and block attack attempts targeting the known vulnerability. If the nature of the vulnerability is well understood, a virtual patch can secure access to the vulnerable application until it can be fully patched.
# Virtual patch example: block POST to a vulnerable endpoint
- name: "VirtualPatch-API-FOO"
action: block
statement:
andStatement:
statements:
- byteMatchStatement:
searchString: "/api/foo"
fieldToMatch:
uriPath: {}
- statement:
andStatement:
statements:
- labelMatchStatement:
scope: LABEL_NAMESPACE
key: "awswaf:managed:aws:bot-control:bot:category:monitored"
visibilityConfig:
sampledRequestsEnabled: true
WAF False Positive Management
When a legitimate HTTP request causes WAF rules to trigger in error, it’s called a false positive. False positives cause poor user experience, make it difficult to detect real attacks, and potentially cause compliance issues.
Reducing False Positives
- Understand the web traffic — know the technologies in use, which HTTP methods are allowed, which file extensions are legitimate
- Disable irrelevant rule categories — if the app doesn’t use PHP or Java, disable those rule groups
- Enable request body inspection only where needed — JSON/XML APIs benefit, but keep body size limits low (16KB is sensible for production)
- Use automated false positive detection — tools that inspect WAF logs and flag the most likely false positive candidates
- Deploy in monitor mode first — observe rule triggers against legitimate traffic before enabling block mode
Testing Your WAF
It’s crucial to test an application through a WAF. Running an automated test suite after updating a web application may flag new false positives caused by new or changed features. Even a simple test answers the common question of “Is my WAF working?”:
# Simple WAF test — triggers Unix command injection rules
curl "https://my-app.example.com/foo?bar=/bin/bash"
# If the WAF is working, a log entry flags "/bin/bash"
# against the test machine's IP address
API Security with WAF
Traditional WAF rule sets were designed for HTML web applications. Modern applications are predominantly API-driven, requiring different protections. When defending APIs, ensure JSON and XML payloads are parsed and inspected.
API WAF Requirements
| Requirement | Implementation |
|---|---|
| Schema validation | Validate request/response against OpenAPI schema |
| Authentication enforcement | Require tokens, validate JWT |
| Rate limiting per client | API keys, OAuth scopes |
| Payload inspection | JSON/XML parsing, body size limits |
| Endpoint-specific rules | Per-path protections |
| Idempotency support | Allow retry-safe write paths |
WAF Bypass Attacks
A WAF can be bypassed. Understanding common bypass techniques helps you build stronger defenses:
| Bypass Technique | Description | Defense |
|---|---|---|
| Encoding tricks | Double URL encoding, Unicode obfuscation | Decode payloads before inspection |
| Case variation | Mixed-case SQL keywords | Case-insensitive rule matching |
| Null bytes | Injecting %00 to truncate | Handle null byte rejection |
| HTTP parameter pollution | Duplicate parameters confuse parsers | Normalize parameter parsing |
| Content-type switching | Change to multipart to skip body inspection | Inspect all content types |
| WAF-parser mismatch | Exploit differences between WAF and app parsers | Use same parser as app |
| Protocol smuggling | CL.TE / TE.CL desync attacks | HTTP/2, strict request validation |
Mitigating Bypass Risk
- Use a WAF that decodes and normalizes payloads before inspection
- Enable body inspection for all content types
- Keep the WAF parser consistent with your application framework
- Deploy defense-in-depth — never rely solely on the WAF
- Run regular bypass testing (fuzzing, payload mutation)
- Monitor for encoding anomalies in traffic
WAF Performance Impact
A poorly tuned WAF can add significant latency. Measure and optimize:
| Factor | Impact | Optimization |
|---|---|---|
| Rule count | More rules = slower | Use managed rule groups, disable irrelevant |
| Body inspection | Parsing payloads is expensive | Limit body size, selective inspection |
| Regex complexity | Catastrophic backtracking | Use bounded regexes, linear algorithms |
| Log volume | Logging every request | Sample logs, log only blocked requests |
| Deployment location | Edge vs origin | Offload to CDN edge |
# Performance targets
waf_performance:
p50_latency_overhead_ms: 5
p95_latency_overhead_ms: 15
max_payload_inspection_kb: 16
rule_timeout_ms: 100
request_sample_rate: 0.05
WAF Logging and Compliance
What to Log
| Event | Required | Retention |
|---|---|---|
| Blocked requests | Yes | 90 days minimum |
| Rule triggers (monitor mode) | Yes | 30 days |
| Rate limit violations | Yes | 30 days |
| Bot detections | Yes | 90 days |
| Bypass attempts | Yes | 1 year |
| Allowed requests (sampled) | Optional | 7 days |
Compliance Frameworks
| Framework | WAF Requirement |
|---|---|
| PCI DSS | WAF or equivalent for all public web apps |
| SOC 2 | Access control + web app firewall controls |
| ISO 27001 | A.14 application security controls |
| HIPAA | EPHI protection for web apps |
| NIST 800-53 | SC-7 boundary protection |
WAF Deployment Configurations
High Availability WAF
Internet
|
[CDN/WAF Edge 1] -- [CDN/WAF Edge 2]
| |
[Load Balancer] ---- [Load Balancer]
| |
[App Server A] [App Server B]
WAF Testing in CI/CD
# GitHub Action: WAF rule testing
name: WAF Rules Test
on: [pull_request]
jobs:
waf-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy test environment
run: docker compose up -d
- name: Run attack simulations
run: |
./tests/waf-attack-test.sh # sends malicious payloads
./tests/waf-normal-test.sh # sends legitimate traffic
- name: Verify no false negatives
run: python tests/check-waf-log.py --expect-blocked
- name: Verify no false positives
run: python tests/check-waf-log.py --expect-allowed
WAF Best Practices
# WAF best practices
deployment:
- "Start in learning/monitor mode"
- "Test in monitor mode before enabling blocking"
- "Tune rules to reduce false positives"
- "Use managed rule groups when possible"
- "Require per-rule, per-path exception capability"
- "Evaluate API schema validation, not just web rules"
- "Keep rules updated"
- "Assess managed rule update cadence against change management"
monitoring:
- "Review blocked requests regularly"
- "Set up alerts for spikes"
- "Track false positive rates"
- "Monitor performance impact"
- "Model total cost including logging, analytics, services"
rules:
- "Start with OWASP Core Rules"
- "Add custom rules for specific needs"
- "Review and update regularly"
- "Test rules before deploying"
- "Use Deprecation/Sunset headers for API changes"
WAF Implementation Checklist
- Deploy in monitor mode first (1-2 weeks of baseline traffic)
- Review false positives and tune rules
- Enable request body inspection for API payloads
- Set paranoia level (PL2 default)
- Configure rate limiting rules
- Enable IP reputation / bot management
- Set up alerting for rule triggers and spikes
- Create virtual patches for known unpatched vulnerabilities
- Document incident response for WAF bypasses
- Schedule regular rule reviews and updates
WAF Rule Examples by Attack Type
SQL Injection
# Block SQL injection patterns
- name: "SQLi-Block"
action: block
statement:
sqliMatchStatement:
fieldToMatch:
body: { oversizeHandling: MATCH }
textTransformations:
- priority: 0
type: "URL_DECODE"
- priority: 1
type: "HTML_ENTITY_DECODE"
Cross-Site Scripting (XSS)
# Block XSS patterns
- name: "XSS-Block"
action: block
statement:
xssMatchStatement:
fieldToMatch:
queryString: {}
textTransformations:
- priority: 0
type: "URL_DECODE"
- priority: 1
type: "HTML_ENTITY_DECODE"
Path Traversal
# Block path traversal
- name: "PathTraversal-Block"
action: block
statement:
byteMatchStatement:
searchString: "../"
searchStringType: "EXACT"
fieldToMatch:
uriPath: {}
Command Injection
# Block command injection
- name: "CommandInjection-Block"
action: block
statement:
regexPatternSetReferenceStatement:
arn: "arn:aws:wafv2:.../regex-pattern-set/command-injection"
WAF Resource Links
| Resource | Type | URL |
|---|---|---|
| OWASP WAF Evaluation Criteria | Guide | waf.owasp.org |
| ModSecurity | Open-source WAF engine | github.com/owasp-modsecurity |
| Coraza | Next-gen Go WAF engine | coraza.io |
| OWASP CRS | Detection rules | coreruleset.org |
| Cloudflare WAF Docs | Documentation | developers.cloudflare.com |
| AWS WAF Docs | Documentation | aws.amazon.com/waf |
Conclusion
WAF is essential for web application security:
- Deploy in stages: Start with monitoring, then block
- Tune continuously: Reduce false positives
- Layer with other security: WAF is one layer of defense
- Use OWASP CRS: The de facto open-source ruleset, at PL2 by default
- Test regularly: Automated test suites catch new false positives
- Virtual patch: Protect unpatched vulnerabilities with custom rules
Use managed rules from Cloudflare, AWS, Azure for best protection. A WAF is not a replacement for patching web application vulnerabilities — it’s a complementary layer that detects and blocks attacks while you keep applications patched and hardened.
WAF Maturity Model
| Level | Capabilities | Testing | Monitoring |
|---|---|---|---|
| 1: Basic | Default rules, block mode | None | Manual log review |
| 2: Tuned | CRS PL2, custom rules | Occasional | Alert on spikes |
| 3: Managed | Managed rule groups, bot detection | Regular test suite | Automated alerts |
| 4: Optimized | API protection, virtual patches | CI/CD testing | False positive analysis |
| 5: Adaptive | ML detection, auto-tuning | Continuous fuzzing | Full observability |
WAF Summary Checklist
- OWASP CRS enabled at PL2
- Monitor mode baseline captured
- Custom rules for app-specific threats
- Managed rule groups subscribed
- Rate limiting configured
- Bot management enabled
- Virtual patches for known CVEs
- CI/CD WAF testing pipeline
- Logging and alerting configured
- Regular rule review scheduled
WAF Rule Design Patterns
Pattern 1: Default Allow with Blocklist
# Allow all traffic, block known bad patterns
default_action: allow
rules:
- name: "Block SQLi"
action: block
match: "sqli"
- name: "Block XSS"
action: block
match: "xss"
Pattern 2: Default Block with Allowlist
# Block everything, allow specific paths
default_action: block
allowlist:
- "/api/public"
- "/health"
- "/assets/*"
Pattern 3: Progressive Enforcement
# Stages: monitor → challenge → block
stages:
- mode: "monitor"
duration: "1-2 weeks"
action: "log only"
- mode: "challenge"
duration: "1 week"
action: "js_challenge on suspicious"
- mode: "block"
duration: "continuous"
action: "block confirmed attacks"
WAF Metrics and Alerting
| Metric | Alert Threshold | Critical |
|---|---|---|
| Blocked requests | > 2x baseline | > 10x baseline |
| False positive rate | > 1% | > 5% |
| Rule trigger count | Spikes | Sustained elevation |
| WAF latency | > 15ms p95 | > 50ms p95 |
| Bot detection | New bot families | Major botnet activity |
| Bypass attempts | > 10/day | > 100/day |
| Managed rule updates | Stale > 30 days | Stale > 90 days |
WAF Comparison: Cloud vs On-Premise
| Factor | Cloud WAF | On-Premise WAF |
|---|---|---|
| Deployment | Edge/CDN, global | Data center, appliance |
| Scaling | Automatic | Manual capacity |
| Latency | Minimal (edge) | Low (same network) |
| Cost | Usage-based | License + hardware |
| Maintenance | Vendor-managed | In-house |
| Visibility | Provider dashboard | Full control |
| Compliance | Provider certifications | Full data control |
| Best for | Public web apps | Regulated, air-gapped |
WAF with API Gateways
Integrate WAF with API gateways for comprehensive API protection:
# API Gateway + WAF integration (AWS)
api_gateway_waf:
stage: "prod"
waf_acl: "api-protection"
rules:
- managed_rule: "AWSManagedRulesCommonRuleSet"
priority: 10
- managed_rule: "AWSManagedRulesSQLiRuleSet"
priority: 20
- managed_rule: "AWSManagedRulesBotControlRuleSet"
priority: 30
- custom_rule: "rate-limit-per-key"
priority: 40
logging:
redacted_fields:
- "authorization"
- "x-api-key"
Frequently Asked Questions
Q: What is the difference between a WAF and a next-generation firewall? A: A WAF inspects HTTP/HTTPS traffic at Layer 7 (application), protecting against web attacks like SQL injection and XSS. A next-generation firewall (NGFW) operates at network layers (L3-L7), providing stateful filtering, intrusion prevention, and application awareness. They complement each other: NGFW at the perimeter, WAF in front of web apps.
Q: Can a WAF replace patching web application vulnerabilities? A: No. A WAF is a compensating control, not a replacement for patching. Virtual patches can protect unpatched vulnerabilities temporarily, but keeping applications patched is essential.
Q: What is the OWASP Core Rule Set and should I use it? A: The CRS is a free, open-source ruleset for WAF engines like ModSecurity and Coraza. It’s the de facto standard for WAF rule detection. Yes, use it — it covers the OWASP Top 10 and common attack patterns.
Q: How do I handle WAF bypass attacks? A: Use a WAF that decodes and normalizes payloads, enable body inspection for all content types, keep parsers consistent with the application, deploy defense-in-depth, and run regular bypass testing.
Q: How does WAF handle API security differently from web application security? A: APIs require schema validation, authentication enforcement, per-client rate limiting, and payload inspection of JSON/XML — beyond the HTML-focused rules of traditional WAFs. Modern WAFs and API gateways provide these API-specific protections.
Related Articles
- Zero Trust Architecture
- Container Security Fundamentals
- DDoS Protection Strategies
- Cloud Security Posture Management
- Next-Generation Firewall
Comments