Choosing Between Managed, Rate-Based, and Custom WAF Rules

November 06, 2028 · 16 min read

Security · SCS-C03 · part of The Exam Room

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

  1. Detection signal, pattern in one request, volume over time, or AWS-curated threat intel?
  2. Maintenance owner, does our team write it or does AWS keep it current?
  3. Cost shape, flat per-ACL, per-rule, or per-request?
  4. Action granularity. Block, Count, CAPTCHA, Challenge?
  5. 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

Credential stuffing on /login ~4000 req/min, ~2000 IPs rotating, 1–3 hits each Traffic signature volume per key over time each request alone is innocent many IPs, repeat hits per IP Rate-based rule aggregate: IP limit: 50 per 5 min scope-down: URI = /login Extra: AWSManagedRulesATP paid managed group catches distributed case where IPs don't repeat SQL injection on /comments single attacker, well-formed payloads, two VPS networks Traffic signature pattern in a single request one hit does the damage throttling is useless AWSManagedRulesSQLiRuleSet matches UNION SELECT, OR 1=1, comment-stripping transforms, body + query + header slots Custom IP set rule block the two VPS ranges priority above managed rule short-circuit the work OWASP Top Ten coverage PCI app, auditor wants coverage maintained against current threat intel Traffic signature broad + evolving SQLi, XSS, LFI, RFI, cmd injection someone must watch CVEs Stack of AWS managed groups CommonRuleSet KnownBadInputsRuleSet SQLiRuleSet + LinuxRuleSet IpReputationList on top cheap pre-filter cuts downstream WCU AWS updates continuously
The attack's shape decides the rule's shape; each row reads left to right as signal → rule type → complementary hardening.

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:

  1. Add each rule group to the Web ACL in Count mode, one at a time, a week apart.
  2. Firehose WAF logs to an S3 bucket, Athena table on top.
  3. 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.
  4. 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).
  5. Once the false-positive queue is empty for a group, flip the group’s OverrideAction from Count to None (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

  1. 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.
  2. 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.
  3. 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.
  4. Pin managed-group versions in production. The Version field lets you review AWS updates before adopting them; without pinning, AWS updates can change behaviour silently.
  5. 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.
  6. Count mode is your canary. Turning a new rule on in Block mode without a Count phase is how false positives become outages.
  7. 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.
  8. Body inspection has size limits. 8 KB default on ALB and API Gateway (bumpable to 64 KB with AssociationConfig), 64 KB default on CloudFront. OversizeHandling decides 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.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.