The situation
We’re running ALBs, CloudFront distributions, API Gateway REST APIs, and an AppSync GraphQL endpoint. Web ACLs are attached to most of them, but the configuration has grown organically over two years: a junior engineer enabled “the managed rules” on one ALB, somebody added a custom IP block list on another, a third has nothing. The security team wants the whole estate on a consistent baseline, and three concrete problems are on the table this quarter:
- Credential stuffing on
/login. ~4,000 requests per minute from a pool of ~2,000 IP addresses, each IP hitting 1-3 times before rotating. Legitimate human traffic to the same endpoint is ~30/minute. - SQL injection attempts on
/comments. Same attacker, same payload shapes (' OR 1=1--and variants), from two VPS providers. Throttling won’t help, one well-formed injection is all it takes. - OWASP Top Ten coverage on the PCI app. The security team needs blocking rules for SQLi, XSS, LFI, RFI, command injection, and the usual deserialisation and SSRF shapes, plus a paper trail showing the rules were current against Q3 2028 threat intelligence.
One WAF service, three problems. Which rule type solves which?
What actually matters
Before reaching for the rule-builder UI, it’s worth thinking about what a WAF rule is and how the different kinds decide a request is hostile.
The core abstraction is: for each incoming request, a rule evaluates a predicate against the request’s attributes (method, URI, headers, body, query string, IP, geo, etc.) and votes Allow, Block, Count, CAPTCHA, or Challenge. A Web ACL is an ordered list of rules; the first rule that matches decides, unless the rule’s action is Count, which is logged-but-doesn’t-short-circuit. The Web ACL’s default action catches everything not matched by any rule.
What changes between rule types is what kind of predicate is practical to express. Some hostile traffic is obvious from a single request, a <script> tag in a query string, a UNION SELECT in a POST body. A good predicate for those is a pattern match against the request content. Some hostile traffic only reveals itself over time, a thousand requests from the same IP in five minutes is suspicious even if each individual request looks fine. A good predicate for those is a counter over a time window. And some hostile traffic is the kind of thing AWS’s threat-intel team sees every day across thousands of customers. IP addresses currently participating in known botnets, common bad-bot user-agents, exploit patterns for CVEs disclosed last week. A good “predicate” for those is a subscription.
Three predicate shapes, pattern, counter, subscription, map to three rule types: custom (JSON or console-built), rate-based, managed. Plus two orthogonal modes every rule can run in: Block (stop it), Count (log it, let it through), and the interactive CAPTCHA and Challenge actions for when we want to verify the client rather than decide for them.
Cost and maintenance are the soft filters. Custom rules are free-to-write but expensive to maintain (who’s watching for the next zero-day?). Managed rule groups carry a subscription charge, sometimes with a per-request inspection fee on top, but they come with a team of researchers keeping them current. Rate-based rules are cheap and largely self-maintaining, but they only work against attacks that show up as volume.
Ordering matters: WAF evaluates rules in Web ACL priority order, stopping at the first Block or Allow. Putting an expensive rule (Bot Control, body inspection) behind a cheap pre-filter (geo match, IP reputation) can cut WCU consumption in half without changing what gets blocked.
What we’ll filter on
- Detection signal, pattern in one request, volume over time, or AWS-curated threat intel?
- Maintenance owner, does our team write it or does AWS keep it current?
- Cost shape, flat per-ACL, per-rule, or per-request?
- Action granularity. Block, Count, CAPTCHA, Challenge?
- WCU cost, does it fit inside the Web ACL’s capacity budget?
The WAF rule landscape
1. Managed rule groups. AWS. AWS publishes a set of named rule groups: AWSManagedRulesCommonRuleSet (the OWASP staples), AWSManagedRulesKnownBadInputsRuleSet, AWSManagedRulesSQLiRuleSet, AWSManagedRulesLinuxRuleSet, AWSManagedRulesAnonymousIpList, AWSManagedRulesAmazonIpReputationList, and a handful more. Each group contains many rules; the group is a single thing to reference in the Web ACL with one priority. Free to use; counts against the 1,500 WCU budget. Updated by AWS Threat Research.
2. Managed rule groups. AWS Marketplace vendors. Fortinet, F5, Imperva, Cyber Security Cloud and others publish WAF rule groups through Marketplace. Vendor-maintained; subscription-priced (flat monthly per group).
3. Managed rule groups. AWS paid (Bot Control, ATP, Fraud Control, Account Creation Fraud Prevention). AWS’s own premium rule groups: Bot Control identifies and categorises bot traffic, Account Takeover Prevention (ATP) detects credential-stuffing on login endpoints by comparing credentials against known-compromised-password databases and scoring login anomalies, Fraud Control / Account Creation Fraud Prevention targets signup abuse. Priced on a base subscription plus per-request inspection.
4. Rate-based rules. “Limit any single IP (or aggregate key) to N requests per 5-minute window.” When a key exceeds the threshold it’s blocked until it falls below. Aggregate key defaults to source IP but can be any forwarded-for header, a header value, a query-string parameter, a cookie, a label from a prior rule, or a composite. Cheap (2 WCU), self-maintaining, counts requests across all protocols and paths unless scoped down.
5. Custom rules, console builder. The visual rule editor. Match on method, URI path, query string, header, body, cookie, country, IP set, regex pattern set, or label. Logical AND, OR, NOT. Build the predicate, pick the action. Good for well-understood patterns. The output is still JSON under the covers.
6. Custom rules, raw JSON. Same rule model, expressed directly. Preferred for anything infrastructure-as-code, anything version-controlled, anything that needs to be identical across Web ACLs. More expressive: some predicates (specific Statement types, text transformations, oversize handling) are easier in JSON than in the visual builder.
7. IP sets and regex pattern sets. Reusable building blocks referenced from rules. An IP set is a list of CIDRs; a regex pattern set is a list of regular expressions. Referenced rather than inlined so one update propagates to every rule that uses it.
Side by side
| Rule type | Signal | Maintained by | Cost shape | WCU per request | Typical use |
|---|---|---|---|---|---|
| AWS managed (free) | Patterns + intel | AWS | $0 extra | 25-200 per group | OWASP baseline |
| AWS managed (paid) | Patterns + ML + creds | AWS | Sub + per-req | 50 + per req | ATP, Bot Control, Fraud |
| Marketplace managed | Patterns + intel | Vendor | Sub | Group-dependent | Specialised coverage |
| Rate-based | Volume per key | You (threshold) | 2 WCU | 2 | Credential stuffing, DDoS L7 |
| Custom JSON | Your patterns | You | 0 (per rule) | 1-20 | Business logic, known-bad payload |
| IP set / regex set | Lookup | You | 0 | 1 ref | Block lists, allow lists |
Reading the table by attack shape:
- Credential stuffing, volume signature per IP over minutes. Rate-based rule aggregated by source IP, threshold 50/5min on
/login. Adding ATP on top catches the distributed version (many IPs, one credential list) that rate-based misses. - SQL injection on /comments, pattern signature in the POST body. AWS managed SQLi rule set handles the common cases; custom JSON rule blocking the specific attacker’s IPs via an IP set handles the immediate attack.
- OWASP Top Ten coverage, broad pattern and intel coverage, maintained by someone who watches threat research full-time. AWS managed rule groups:
CommonRuleSet+KnownBadInputsRuleSet+SQLiRuleSet+LinuxRuleSet(if the app runs on Linux) +IpReputationList. Five managed groups, ~1,000 WCU, covers the auditor’s question.
Three attacks, three rule shapes
The picks in depth
Rate-based for credential stuffing. A single rule: aggregation key IP, limit 50 per 5-minute window, evaluation window 300 seconds, scope-down statement restricting the count to URI_PATH = /login AND METHOD = POST. At 2 WCU, this is the cheapest rule in the catalogue, and it solves the attack shape directly. Two gotchas: the 5-minute window is fixed (WAF rate-based supports 60, 120, 300, 600 seconds as of Q2 2025; don’t pick 60 for login flows because a human retrying 3-4 times will hit it); and aggregation by IP is insufficient against the distributed case where 5,000 IPs hit /login once each. For that, aggregate by something the attacker can’t rotate, an HTTP header that carries a session cookie for the login form, or a composite key of (forwarded-for IP, User-Agent). When even that breaks down, it’s time for ATP.
AWSManagedRulesATPRuleSet for distributed stuffing. The paid managed group for Account Takeover Prevention. It works by inspecting login POST bodies, comparing the submitted credential against AWS’s database of known-leaked credentials (populated from public breach corpora), and computing a behavioural score across a rolling window for the username field regardless of source IP. The rule emits labels like awswaf:managed:aws:atp:signal:credential_compromised and awswaf:managed:aws:atp:signal:volumetric_session; a follow-up custom rule can block on label. Priced at a flat monthly subscription plus per-login-request inspection. The configuration needs the endpoint’s login path, the JSON field names for username and password, and the RequestInspectionACFP block.
Managed SQLi rule set for injection. AWSManagedRulesSQLiRuleSet inspects query string, URI, header values, and request body for SQLi patterns with the appropriate text transformations (URL-decode, HTML-entity-decode, compress whitespace, lowercase). It catches the textbook attacks and most variations; it’s not infallible against highly tailored payloads but it’s a solid baseline. Put it in the Web ACL at a priority after a cheap IP-set block (the known attacker’s two VPS ranges) so the cheap rule short-circuits before the expensive inspection runs. Body inspection has a default 8 KB limit for ALB and API Gateway origins and 64 KB for CloudFront; requests larger than that are evaluated on the inspectable prefix and match only if the attack is in that prefix. Setting OversizeHandling to MATCH (treat oversized requests as matching the rule) errs on the side of blocking; NO_MATCH errs on the side of letting them through; CONTINUE evaluates on the prefix.
AWS managed stack for OWASP coverage. Five rule groups in this order: AWSManagedRulesAmazonIpReputationList (priority 0, cheap), AWSManagedRulesCommonRuleSet (priority 10), AWSManagedRulesKnownBadInputsRuleSet (priority 20), AWSManagedRulesSQLiRuleSet (priority 30), AWSManagedRulesLinuxRuleSet (priority 40). Total ~1,100 WCU, well within the 1,500 budget. Two practical notes: enable each group in Count mode for a week before flipping to Block to find and exclude any rules that false-positive against real traffic; and pin rule-group versions in production via the Version field so AWS updates don’t silently change behaviour (keep a regular calendar to review and bump versions deliberately).
A worked Web ACL
The PCI application’s Web ACL, in priority order:
Priority 0: IP set "block-known-bad" (custom)
- block list maintained by the security team
- 5 WCU, BLOCK
Priority 10: Rate-based "login-throttle"
- aggregate: IP, scope: URI=/login, limit: 50/5min
- 2 WCU, BLOCK
Priority 20: AWSManagedRulesATPRuleSet
- paid managed group, login endpoint = /login
- ~50 WCU + per-request inspection, BLOCK
- action override: credential_compromised -> CAPTCHA
Priority 30: AWSManagedRulesAmazonIpReputationList
- AWS managed (free)
- 25 WCU, BLOCK
Priority 40: AWSManagedRulesCommonRuleSet
- AWS managed (free)
- 700 WCU, BLOCK
- excluded rules: SizeRestrictions_BODY (legit large uploads)
Priority 50: AWSManagedRulesKnownBadInputsRuleSet
- AWS managed (free)
- 200 WCU, BLOCK
Priority 60: AWSManagedRulesSQLiRuleSet
- AWS managed (free)
- 200 WCU, BLOCK
Priority 70: AWSManagedRulesLinuxRuleSet
- AWS managed (free)
- 50 WCU, BLOCK
Default action: ALLOW
Logging: to Firehose -> S3 (KMS-encrypted)
Total WCU ~1,230 of 1,500. The ordering matters: the cheap IP block and rate limit run first and short-circuit most bad traffic before a request ever touches the expensive managed groups. The Web ACL is deployed via CloudFormation, the rule-group versions are pinned, and the logging destination feeds a weekly false-positive review.
A worked shadow-mode rollout
Flipping five managed rule groups to Block simultaneously is how you cause a Sev 1. The pattern:
- Add each rule group to the Web ACL in Count mode, one at a time, a week apart.
- Firehose WAF logs to an S3 bucket, Athena table on top.
- Weekly query:
SELECT terminatingRuleId, httpRequest.uri, COUNT(*) FROM waf_logs WHERE action = 'COUNT' AND time_between last_seven_days GROUP BY 1, 2 ORDER BY 3 DESC. - Top results surface the rules most likely to cause pain. Legitimate false positives get excluded via the rule group’s
RuleActionOverrides(set that specific rule to Count while the rest of the group blocks). - Once the false-positive queue is empty for a group, flip the group’s
OverrideActionfromCounttoNone(which means “use the rule’s configured action”. Block, for these groups).
The pattern is borrowed from any production rollout: canary first, measure, decide, promote. WAF gives us Count as the canary mechanism at the rule-group level; not using it is how production outages happen.
What’s worth remembering
- The rule’s shape has to match the attack’s shape. Volume signatures get rate-based rules; pattern signatures get custom or managed pattern rules; broad/evolving signatures get managed rule groups. Swapping these wastes effort.
- Rate-based rules are cheap (2 WCU) and self-maintaining but only see volume. Aggregation key defaults to source IP; distributed attacks need composite keys or ATP.
- AWS managed rule groups fall into three pricing buckets. Free baseline groups (
CommonRuleSet,KnownBadInputsRuleSet, etc.), paid AWS groups (ATP, Bot Control, Fraud Control), and third-party Marketplace groups. Free groups cover OWASP; paid groups cover credential stuffing and bot categorisation. - Pin managed-group versions in production. The
Versionfield lets you review AWS updates before adopting them; without pinning, AWS updates can change behaviour silently. - Web ACL priority controls evaluation order and short-circuits expensive work. Cheap pre-filters (IP sets, geo match, rate-based) first; expensive pattern-matching (managed groups, body inspection) after.
- Count mode is your canary. Turning a new rule on in Block mode without a Count phase is how false positives become outages.
- The 1,500 WCU budget is real. Plan the Web ACL’s total WCU; exceeding the limit prevents adding rules. A typical OWASP-coverage stack lands around 1,100-1,200.
- Body inspection has size limits. 8 KB default on ALB and API Gateway (bumpable to 64 KB with AssociationConfig), 64 KB default on CloudFront.
OversizeHandlingdecides what to do with requests bigger than the inspectable prefix.
Three attack shapes, three rule types. The work is recognising which is which and stacking the Web ACL so the cheap rules do the easy filtering and the expensive rules only see what survived.