In short

Treat a WAF as a tuned filter, not as the application's authorization. When OWASP CRS blocks legitimate traffic, read the matched rule IDs and the target, then exclude only that rule or tag for that parameter on that route; never switch the engine off for the route. Start at paranoia level 1 with the default anomaly threshold of 5, keep the application fix that the exclusion now relies on, and handle bots by the business action they abuse.

  • CRS adds a score for every matched rule and blocks when the inbound total reaches the threshold, 5 by default.
  • Exclude by rule ID or tag, parameter and route: ctl:ruleRemoveTargetByTag=attack-xss;ARGS:comment kept SQL injection checks on that field.
  • An exclusion moves the defense to the application: after it, a real <script> payload in that field also passed, so output encoding must hold.
  • Paranoia levels 1 to 4 trade false positives for coverage; CRS 4 can detect at a higher level than it blocks while you tune.
  • Bots abuse valid requests: map them to OWASP OATs such as OAT-008 credential stuffing and OAT-021 denial of inventory, and limit the business action.

Verify it

  1. Reproduce the false positive and read the matched rule IDs and targets from the ModSecurity audit log before writing any exclusion.
  2. After the exclusion, confirm the legitimate request passes and that an attack of another class in the same field, the same text in another field and the same field on another route are still blocked.
  3. Send a real exploit payload in the excluded field and confirm the application encodes or rejects it, because the WAF no longer will.
  4. Run the change in detection-only or with a higher detection paranoia level first and compare anomaly scores on real traffic.
  5. For each bot-facing flow, record which OAT it addresses and test the per-account limit, not only the per-address one.
Applies to
  • OWASP CRS 4.30.0 (tested in the owasp/modsecurity-crs:nginx image with ModSecurity and nginx 1.30.5)

WAF and bot-rule operation. Managed signatures cannot establish application authorization or business invariants.

Learning goals and prerequisitesAbout 28 minutes: 8 to read and 20 for the exercise

What you will learn

  • Design an exclusion around one false positive without disabling unrelated inspection
  • Choose limits for distributed credential stuffing that a per-address rate limit cannot see
  • Distinguish pattern filtering from business authorization

Before you start

Platform and security engineers who operate a WAF or CDN rule set and the teams that own the applications behind it

  • Distinguish edge decisions, origin behavior, false positives and business abuse.

Refresh first: Maintain an API inventory and prevent gateway bypass

Go to the guided practice

An edge control sees a different view from the application

A WAF inspects traffic before the application's full business context is available. It can reduce exposure to recognized patterns and provide temporary containment, but it cannot reliably infer every valid account relationship or repair a transaction race. Treat a virtual patch as a tracked mitigation with a linked application fix and retirement condition.

RASP observes execution inside a running application, while IAST uses instrumentation during testing to find defects. These locations change available context, deployment cost and failure behavior. Evaluate each against a concrete task rather than assuming that adding another category guarantees complete protection.

Worked exclusion: a comment field contains legitimate punctuation

The synthetic rule model below, with invented rule names, keeps the exercise small; the next section shows the same pattern with the real OWASP CRS. R-A flags an apostrophe in any body field, while R-B flags the literal test marker BAD_TEST in any body field. A legitimate comment, O'Connor's booking, trips the simplistic R-A. Disabling all inspection on the route would also disable R-B and other controls. Excluding only R-A on the comment parameter preserves the rest of the modeled inspection.

Real CRS tuning requires the actual matched rule, target variable, transformation and anomaly score. One rule can inspect many parameters, and multiple matches may contribute to a blocking score. Capture a redacted reproducible legitimate request and compare narrowly scoped exclusions under the installed ruleset. The intended lesson is how to define scope and regression cases, not to recommend removing a named production rule without evidence.

The application must still use safe database APIs and enforce ownership. A WAF cannot infer that authenticated North user N may not cancel South user S’s booking when both requests are syntactically ordinary. Keep an application regression for that relationship even if the edge blocks known injection patterns. Track a temporary virtual patch with its owner, expiry, affected route and application repair.

Bots are the other half of edge work, and signatures rarely see them. In the exercise trace, 40,000 login attempts arrive in an hour from 9,800 residential addresses, none sending more than five, each with a different leaked username and password pair. Every request is well-formed, so no CRS rule matches, and a limit of 20 requests a minute per address never fires. This is OWASP OAT-008 credential stuffing. The controls that bite key on what the attacker reuses or targets, not on the address: failed logins per account and per username, a check of submitted passwords against known breached passwords, and phishing-resistant MFA or passkeys so a correct password is not enough. The MFA and login defense guide at /domains/identity-crypto/authentication/mfa-account-takeover/ covers those controls.

Plain text
SYNTHETIC RULES, not production CRS identifiers
R-A: any ASCII apostrophe in any body field -> match
R-B: literal BAD_TEST in any body field -> match
Any non-excluded match blocks
Candidate exclusion: R-A only, path=/comments, parameter=comment
T1 /comments comment="O'Connor"
T2 /comments comment="BAD_TEST"
T3 /comments username="O'Connor"
T4 /profile comment="O'Connor"

Write a narrow exclusion for OWASP CRS

With CRS 4.30 running on ModSecurity at paranoia level 1, a developer-forum comment, wrap it in <script> tags, posted to /comments was blocked with 403. The audit log named three matches on ARGS:comment (rules 941100, 941110 and 941160, all tagged attack-xss) for an anomaly score above the threshold of 5, which rule 949110 enforced. The exclusion below removes the XSS rules from that one parameter on that one route.

Tested with the same requests as the exercise: the legitimate comment passed; a SQL injection string in the same field, the same HTML in a username field and the same comment posted to /profile were all still blocked. A route-wide ctl:ruleEngine=Off also let the legitimate comment through, but it let the SQL injection and the username case through as well. Even the narrow exclusion has a cost: <script>alert(1)</script> in the comment field now passed the WAF, so the application must encode comments on output, and that test belongs in its regression suite.

ConfigurationREQUEST-900-EXCLUSION-RULES-BEFORE-CRS.confApache config
# Developers post HTML snippets in the comment field of POST /comments. Stop the XSS rules
# from inspecting that one parameter on that one route; every other rule still applies to it,
# and every rule still applies to other parameters and routes.
SecRule REQUEST_FILENAME "@streq /comments" \
    "id:10000,\
    phase:1,\
    pass,\
    nolog,\
    ctl:ruleRemoveTargetByTag=attack-xss;ARGS:comment"

Tune narrow exceptions with representative traffic

Start with a documented ruleset and a controlled observation phase. CRS groups rules into paranoia levels 1 to 4: level 1 aims for few false positives, and each higher level adds stricter rules that catch more attacks and more legitimate traffic. CRS 4 separates tx.blocking_paranoia_level from tx.detection_paranoia_level, so rules from a higher level can score and log without blocking while you tune. By default it blocks a request whose inbound anomaly score reaches 5 and a response whose score reaches 4. OWASP CRS uses rule matches and anomaly scoring; review which inputs triggered which rules instead of disabling a broad protection because a legitimate request was blocked. Keep a fixture for the legitimate request and one for the intended rejection when introducing an exclusion.

Limit exclusions to the necessary endpoint, parameter and rule where the implementation supports that scope. Verify limits and body inspection behavior for uploads, compressed data and large payloads. Understand what the edge does when inspection fails or a backend is unavailable. Monitor false-positive impact on real tasks, not only the percentage of traffic blocked.

Keep intermediaries consistent about the request and response

Request smuggling arises when components disagree about request boundaries or framing; the guide at /domains/runtime/edge-defense/http-request-desynchronization/ shows how to test a chain. Use maintained proxy/server implementations, reject ambiguous framing and verify HTTP-version translation. Parser disagreement can undermine routing and WAF assumptions even when the visible request looks ordinary. Run such checks only in an isolated environment because shared connections and caches can affect other traffic.

Shared caches at the edge are a separate boundary: a cache that stores a personalized response under an incomplete key leaks it to the next user without any malicious request. The cache poisoning and deception guide at /domains/runtime/edge-defense/cache-poisoning-deception/ covers cache keys, private responses and how to test them; no WAF rule or threshold repairs a cache key.

Rate-limit the business consequence as well as traffic

Credential stuffing, scraping and inventory hoarding may use syntactically valid authenticated requests. Combine account/tenant/action limits, risk signals and suitable authentication controls with upstream capacity protection. Avoid permanent account lockouts that let an attacker deny access to a victim. Challenges need accessible alternatives and measured failure rates. Test recovery after the abusive pattern stops and keep application permission checks independent of the edge decision.

The OWASP Automated Threats to Web Applications project names 21 kinds of abuse, OAT-001 to OAT-021, which gives teams a shared vocabulary for which flow is attacked and which control applies. Most need application or identity controls rather than WAF signatures.

Rate-limit the business consequence as well as traffic
OWASP OATAbused flowControls that limit it
OAT-008 Credential StuffingLogin with leaked username and password pairsPhishing-resistant MFA or passkeys, breached-password checks, per-account and per-network limits
OAT-007 Credential CrackingGuessing passwords for known accountsPer-account throttling with backoff instead of permanent lockout
OAT-019 Account CreationBulk sign-ups for spam or bonusesVerification of a contact channel, per-network limits, delayed privileges
OAT-011 ScrapingBulk collection of pages or API dataAuthenticated access, per-account quotas, pagination limits
OAT-021 Denial of InventoryHolding stock or seats without buyingPer-account hold limits and short hold expiry
OAT-005 ScalpingBuying limited goods faster than people canPer-account purchase limits, queueing, verified buyers

Diagnose incomplete fixes

A request that exceeds the inspection body limit may be rejected, partially inspected or passed according to the product and configuration. Test that explicit failure mode. A dashboard showing inspected requests tells you little about traffic skipped by the inspection path.

Bot controls also need a defined unit of abuse. Account recovery, reservation holds and expensive search may require distinct per-account, tenant and action budgets. A challenge or IP limit that blocks a shared office network can harm legitimate users while leaving distributed abuse intact. Measure successful user tasks and recovery after pressure stops.

Check the result against concrete cases

These outcomes belong to the synthetic model; the CRS test above produced the same pattern for T1 to T4 with real rules.

Check the result against concrete cases
CaseExpected resultWhat it establishes
T1 with narrow exclusionPass these modeled rulesLegitimate comment restored
T2Block R-BOther rule on same parameter retained
T3Block R-AOther parameter remains inspected
T4Block R-AOther endpoint remains inspected
Ordinary cross-owner cancellationApplication denies ownershipBusiness authorization remains independent
40,000 logins from 9,800 addresses, five or fewer eachPer-account and breached-password controls engage; the per-address limit alone never firesBot controls key on the abused account, not only the address

Guided practice

Evaluate the four rule fixtures exactly under the synthetic model, then decide how to stop the login abuse described below.

Supplied scenario material
Plain text
Exclusion E1: disable every rule for /comments.
Exclusion E2: disable R-A only for /comments parameter comment.
B1 POST /login: 40,000 attempts in one hour; 9,800 distinct client addresses; at most 5 per address.
B2 Each attempt uses a different username and password pair; 61% of usernames exist; 0.4% of attempts succeed.
B3 Current controls: WAF with CRS at paranoia level 1; rate limit 20 requests per minute per address; no MFA.
A normal authenticated request by N attempts to cancel a booking owned by S.
  1. 1. Compare exclusions

    Apply E1 and E2 to T1–T4 and explain which preserves the intended test rejection.

    Expected result and reasoning

    E1 lets T1, T2 and T3 through on /comments and still blocks T4. E2 lets only T1 through; T2, T3 and T4 remain blocked. E2 preserves the modeled BAD_TEST rejection and other parameter/route checks.

  2. 2. Choose the bot controls

    Use B1 to B3 to explain why the current controls miss the attack and which controls would limit it.

    Expected result and reasoning

    Every request is syntactically normal, so CRS has nothing to match, and no address exceeds the per-address limit. This is credential stuffing (OAT-008). Add per-account and per-username failure limits with backoff rather than permanent lockout, check passwords against breached-password lists, require phishing-resistant MFA or passkeys, and treat the accounts with successful logins as compromised: reset their sessions and credentials.

  3. 3. Keep the application test

    Decide whether passing edge inspection authorizes N’s cancellation.

    Expected result and reasoning

    No. The application must check the booking owner or delegated permission and deny N under the stated ownership model. Pattern inspection has no supplied authority to grant that business action.

  4. 4. Define release evidence

    Specify positive and negative checks for the final repair.

    Expected result and reasoning

    Retain T1 as a legitimate successful request and T2 to T4 as rejection controls under the model. Replay a distributed login test from many addresses and confirm the per-account limit and MFA stop it while a real user still signs in. Verify an owner can cancel and a non-owner cannot.

Your deliverable: The T1 to T4 results under E1 and E2, the bot controls with the key each one limits on, the application ownership decision and the release checks.

The rule evaluation uses the synthetic model; the CRS exclusion on this page shows the same scoping with real rules.

Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.

No draft saved on this device yet.

Run a local project

Download and unzip a starter with failing tests and a step-by-step README, edit the named exercise file, and run the tests until they pass. The reference solution is a separate download, so you can attempt the repair before you see it.

Node.js 22+ · loopback servers · no dependencies · 45–60 min

Repair a real proxy and shared cache

Reproduce a private-response leak, repair cache policy and verify HTTP framing with real origin/proxy servers and raw socket requests.

In the extracted folder, run:

Shell
node observe.mjs
node --test exercise.test.mjs

12 regression tests · The starter intentionally fails some cases.

Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.

Part of the learning path Browser and API boundaries

Actual HTTP/1.1 loopback traffic and a narrow cache contract; it is not a full HTTP cache or a test of your production intermediary chain.

Sources & further reading

The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.

  1. OWASP CRS anomaly scoring
  2. OWASP CRS false-positive tuning
  3. RFC 9112 HTTP framing
  4. RFC 9111 caching
  5. OWASP Credential Stuffing Prevention
  6. OWASP CRS: paranoia levels
  7. OWASP CRS Docker images
  8. OWASP Automated Threats to Web Applications

Reference information

SDLC phase
Maintenance
ASVS 5.0.0 requirements
v5.0.0-2.4.1 · v5.0.0-6.3.1

Download this guide as Markdown