WAF

Overview

The WAF uses Coraza SPOA via HAProxy’s SPOE filter mechanism. It provides OWASP CRS protection, custom SecRules, conditional exceptions, rule version snapshots, and remote rule set management — all managed from the coreX Platform UI.

Rule Set Types

coreX Platform supports three types of WAF rule sets:

CRS (OWASP CRS)

The OWASP Core Rule Set is bundled in the coraza-spoa image. CRS provides broad protection against common web attacks (SQL injection, XSS, RCE, LFI/RFI, etc.) using anomaly scoring.

Custom

Custom rule sets contain only sec_rules — no includes, no CRS. Use these for application-specific rules that don’t fit the CRS model.

Remote

Remote rule sets download .conf files from a URL with SHA256 verification and optional auto-update on a configurable interval. This is useful for subscribing to third-party rule feeds.

WAF Rules

WAF rules are created per listener (and optionally per backend). Each rule specifies:

PropertyDescription
NameDisplay name (unique)
ListenerWhich listener to protect
BackendOptional backend scoping (rule only applies to one backend)
Rule setCRS, Custom, or Remote
EngineOn, DetectionOnly, or Off
Paranoia levelCRS paranoia level (1–4)
Inbound anomaly thresholdScore at which inbound requests are blocked
Outbound anomaly thresholdScore at which outbound responses are blocked
ActionWhat to do when the threshold is exceeded
Status codeCustom HTTP status code for blocked responses
Path patternOptional path prefix/regex scope (e.g. /api/)
HTTP methodsOptional comma-separated method scope (e.g. GET,POST)
Content typesOptional content-type scope (e.g. application/json)
Fail openWhen enabled, allow the request if the SPOA is unreachable (default: fail closed)
Rate-based ruleEnable per-rule rate limiting (see below)
Custom SecRulesFree-text Coraza SecRules appended to the generated config
EnabledToggle without deleting

Actions

ActionDescription
blockReturn 403 (or the custom status code)
allowPermit the request (skip remaining WAF rules)
logLog but allow
redirectRedirect the client to a configured URL
challengePresent a CAPTCHA challenge (see CAPTCHA)

Exceptions

WAF exceptions let you selectively disable or modify specific CRS rules for specific requests — the standard mechanism for handling false positives without lowering the global anomaly threshold or disabling the WAF entirely.

Exception Actions

Each exception has one of four actions:

ActionDescription
removeSkip the matched rule entirely for matching requests (SecRuleRemoveById)
allowSkip the rule only for a specific variable/zone (e.g. exclude ARGS:search from rule 942100) — uses ctl:ruleRemoveTargetById so the rule still runs on other variables
commentDisable the rule by ID with a comment marker
updateChange the rule’s action for matching requests (e.g. force pass instead of deny) and optionally exclude a target variable

Rule Selection

An exception targets one or more rules by any combination of:

FieldDescription
Rule IDCoraza rule ID (e.g. 942100). Multi-value — one exception can target multiple rule IDs.
Rule TagCoraza rule tag (e.g. attack-sqli). Multi-value.
Rule MessageMatch by rule message substring. Multi-value.

The suggestion catalog (rule IDs, tags, messages, and zones) is loaded from the backend when the exception editor opens, so the multi-value inputs offer autocomplete suggestions drawn from the active rule set.

Variable Exclusion (allow action only)

When the action is allow, you can scope the exclusion to a specific variable so the rule still runs on everything else:

FieldDescription
ZoneCoraza zone: ARGS, REQUEST_HEADERS, REQUEST_COOKIES, ARGS_NAMES, etc. (multi-value)
VariableThe specific variable name within the zone (e.g. search, password)
Matcherequals, contains, regex, or startsWith — how the variable value must match
ValueThe value to match against the variable (only required when a matcher is set)

Condition (optional)

A condition gates the entire exception on a request attribute, so the exception only applies when the condition matches. Without a condition the exception is unconditional.

FieldDescription
Condition variableThe request attribute to test (e.g. REQUEST_URI, REQUEST_METHOD, REMOTE_ADDR)
Condition operatorequals, contains, startsWith, regex, gt, lt
Condition valueThe value to compare against (e.g. /api/search for REQUEST_URI startsWith)

Live Preview

The exception editor shows a live preview of the Coraza directives the exception will generate (e.g. SecRuleRemoveById 942100 or a chained SecRule with ctl:ruleRemoveTargetById). The preview updates as you edit, so you can verify the exception does what you intend before saving.

Creating an Exception from a WAF Log Event

The fastest way to create an exception is from a logged false positive:

  1. Navigate to Security > WAF > Logs
  2. Find the false-positive event (use the search box to filter by rule ID, URI, or message)
  3. Click the row to expand it
  4. Click Create exception — this opens the exception editor pre-filled with:
    • Rule ID from the log event
    • Condition variable = REQUEST_URI, operator = startsWith, value = the event’s request URI
    • Name = exception-<rule_id>
  5. Adjust the condition (e.g. narrow the path prefix) and save
  6. Apply changes
Why start from a log event?

The log event already contains the exact rule ID and the URI that triggered the false positive, so the pre-filled exception is correct by construction. You only need to decide how broad the path condition should be — keep it narrow to avoid masking real attacks on other endpoints.

WAF Rule Scoping

An exception can be scoped to a specific WAF rule (selected in the WAF Rule dropdown) or left Global so it applies across all WAF rules on all listeners.

Rule Version Snapshots

coreX Platform can save and restore the full configuration of a single WAF rule. This is useful for:

  • Auditing changes over time
  • Rolling back a bad configuration change
  • Comparing before/after states

Auto-pruning keeps the N most recent snapshots per rule (configurable).

Per-Rule Rate Limiting

Each WAF rule can optionally rate-limit clients that trigger it, using a sliding-window counter backed by HAProxy stick-tables. This lets you progressively block or challenge abusive clients that repeatedly trigger WAF rules without affecting well-behaved clients.

Enable Rate-based rule in the WAF rule editor (only available for the block and challenge actions). When enabled, the rule tracks exceedances per client and applies the rate action once the threshold is crossed.

FieldDescription
Rate eventsNumber of WAF trigger events allowed within the window (default 100)
Window (seconds)Sliding window length in seconds (default 60)
Rate keyWhat to count per: src (client IP, default), user_id (tracks X-User-ID or a custom header), header (tracks a custom header), path (tracks the request path)
Rate headerHeader name when rate_key is user_id (defaults to X-User-ID) or header
Rate actionblock (return 403) or challenge (present a CAPTCHA) when the rate is exceeded
Block duration (seconds)Tarpit duration after first exceedance. 0 = sliding-window only (the client is blocked only while their rate stays above the threshold). >0 = the client is blocked for that many seconds after the first exceedance, even if their rate drops.

How the sliding window works

  • The counter uses HAProxy’s gpc0_rate({window}s) / gpc1_rate({window}s) stick-table stores, which are true sliding-window rates (not lifetime counters). A client is blocked only while their rolling event count exceeds the threshold.
  • src keys use the IP-type stick table on sc0 (shared with listener rate limiting). user_id, header, and path keys use a separate string-type stick table backend (waf_rate_{listener_name}) tracked on sc1.
  • When Block duration is greater than 0, a separate block_table_{listener_name} stick table on sc2 records the first-exceedance timestamp so the block persists for the configured duration even after the rolling rate drops below the threshold.
WAF rate limiting vs listener rate limiting

WAF per-rule rate limiting counts WAF trigger events (requests that matched a WAF rule), not all requests. Use listener rate limiting to cap total request volume per client, and WAF per-rule rate limiting to progressively block clients that keep tripping the WAF. The two features use different stick-table counters (sc0/sc1/sc2) and can be combined on the same listener.

CRS Downloader

The CRS downloader fetches official OWASP CRS releases from GitHub. Features:

  • Download specific CRS versions
  • Rollback to previous versions
  • View release notes
  • Automatic update checks
CRS versioning

Each CRS version is stored separately. You can switch between versions without re-downloading. The active version is the one referenced by WAF rules that use the CRS rule set type.

Remote Rule Set Downloader

For remote rule sets:

  • Download .conf files from a URL
  • Verify SHA256 checksum
  • Auto-update on a configurable interval
  • Manual refresh button for immediate updates

WAF Logs

The WAF Logs page shows expandable rows with full Coraza event details:

  • Rule ID, message, severity
  • Client IP, URI, method
  • Matched data / variables
  • Action taken
  • Timestamp

Inline search lets you filter by any field.

WAF Metrics

WAF events are sampled into the database for dashboard breakdowns:

  • Breakdowns: action, rule ID, severity, message
  • Stacked bar chart over time
  • Totals table with counts per category
  • Configurable time range

Blocking Behavior

SecRuleEngine placement

SecRuleEngine On must be set after all includes. If it appears before includes, the engine may not pick up the included rules correctly. coreX Platform handles this automatically in generated configs.

SecDefaultAction

Do not duplicate SecDefaultAction. CRS defines its own default action. Adding a conflicting default action can cause unexpected behavior. coreX Platform validates this before apply.

Custom Error Pages

WAF can serve custom error pages for 403 (blocked) and 500 (internal error) responses. Template variables available:

VariableDescription
{{ request_id }}HAProxy unique request ID
{{ waf_unique_id }}Coraza transaction ID

Step-by-Step: Enable Per-Rule Rate Limiting

  1. Navigate to Security > WAF and edit an existing WAF rule (or create one)
  2. Set Action to block or challenge (rate limiting is only available for these actions)
  3. Enable Rate-based rule
  4. Rate events: 10 (allow 10 WAF triggers per window)
  5. Window (seconds): 60
  6. Rate key: src (count per client IP)
  7. Rate action: block (return 403 when exceeded) or challenge (present a CAPTCHA)
  8. Block duration (seconds): 300 (tarpit the client for 5 minutes after the first exceedance)
  9. Save and Apply

Now a client that triggers the WAF rule more than 10 times in 60 seconds is blocked (or challenged) for all subsequent requests that match the rule, for 5 minutes.

Step-by-Step: Enable WAF with CRS

  1. Navigate to Security > WAF
  2. If no CRS is downloaded, click Download CRS (latest version)
  3. Click Add WAF Rule
  4. Name: protect-web
  5. Select your listener
  6. Rule set: CRS
  7. Anomaly threshold: 5 (start conservative)
  8. Action: block
  9. Click Save
  10. Click Apply Changes
Start with log-only

When deploying WAF for the first time, set the action to log and monitor the WAF Logs page for false positives. Once you’re confident, switch to block. Add exceptions for any legitimate traffic that triggers rules.

Step-by-Step: Add an Exception

  1. Navigate to Security > WAF > Exceptions
  2. Click Add Exception
  3. Name: skip-sqli-on-search
  4. Action: remove (skip the rule entirely) or allow (skip only a specific variable)
  5. Rule ID: 942100 (SQL injection detection) — use the autocomplete suggestions
  6. (Optional) Condition variable: REQUEST_URI, operator: startsWith, value: /api/search
  7. (Optional, allow action only) Zone: ARGS, Variable: search — exclude only the search parameter
  8. Review the live preview of the generated Coraza directives
  9. Save and Apply

Step-by-Step: Create an Exception from a Log Event

  1. Navigate to Security > WAF > Logs
  2. Search for the false-positive event (e.g. filter by rule ID)
  3. Click the row to expand it
  4. Click Create exception
  5. The exception editor opens pre-filled with the rule ID and a REQUEST_URI startsWith <event-uri> condition
  6. Narrow the condition if needed (e.g. shorten the path prefix)
  7. Review the live preview and save
  8. Apply changes

Verification

  1. Test with a malicious request:

    curl -k "https://localhost/?id=1' OR '1'='1"

    Expect a 403 (or log entry if in log-only mode).

  2. Check WAF Logs:

    • Navigate to Observability > WAF Logs
    • Verify the event appears with rule ID and details
  3. Check WAF Metrics:

    • Navigate to Observability > Metrics > WAF
    • Verify events appear in the breakdown chart
  4. Test with a benign request:

    curl -k https://localhost/

    Expect a normal 200 response.

Next Steps