CrowdSec Appsec doesn't process JSON/XML

Dear Community,

During the migration to Crowdsec AppSec engine, we’ve noticed that JSON/XML request body headers aren’t processed automatically. It used to work for NGINX + ModSec + CRS stack.

Environment:

  • CrowdSec version: v1.6.7+

  • AppSec component with Coraza WAF engine

  • CRS v4.0.0

  • Any reverse proxy (nginx, caddy, traefik, etc.)


Description

CrowdSec AppSec with Coraza does not process JSON or XML request bodies by default. When a POST/PUT request with Content-Type: application/json is received, Coraza falls back to URLENCODED body processor, treating the entire JSON body as a single URL-encoded key with no value.

This is a silent misconfiguration that affects every CrowdSec AppSec installation protecting JSON APIs and has two serious consequences:

  1. False positives — The entire JSON body becomes a single ARGS_NAMES value, which can trigger WAF rules (e.g. CRS rule 942550 — JSON-Based SQL Injection) on legitimate traffic because the raw JSON string contains tokens that match attack patterns

  2. Inspection blind spots — Malicious content inside JSON field values is never inspected, since values are not parsed at all

Root Cause

Coraza by design never implicitly processes JSON or XML bodies, as stated in its documentation:

“JSON and XML processors are never used implicitly. Instead, you must tell Coraza to use it by placing a few rules in the REQUEST_HEADERS processing phase.”

Unlike ModSecurity, which auto-detects application/json and processes it accordingly, Coraza requires an explicit rule to activate the JSON body processor.

The NGINX ModSecurity connector historically shipped these rules as connector-level defaults in modsecurity.conf:

apache

# Enable JSON request body parser.
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
  "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"

# Enable XML request body parser.
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
  "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"

CrowdSec AppSec ships no equivalent defaults, leaving JSON and XML body inspection silently disabled.


Reproduction Steps

  1. Set up CrowdSec AppSec with Coraza + CRS protecting any application with a JSON API

  2. Send a POST or PUT request with Content-Type: application/json and a JSON body

  3. Add a debug rule to inspect how the body is parsed:

apache

SecRule ARGS_NAMES "@rx ." \
  "id:9998,phase:2,pass,log,msg:'ARGS_NAME: %{matched_var}'"
```
4. Observe that instead of individual field names, the entire JSON body appears as a single `ARGS_NAMES` value
5. Confirm that `ARGS` values are empty — JSON field values are never inspected

---

**Expected Behavior**

A `POST` request with body `{"id":"123","name":"test"}` and `Content-Type: application/json` should result in:
```
ARGS_NAMES: json.id
ARGS_NAMES: json.name
ARGS:json.id = 123
ARGS:json.name = test
```

**Actual Behavior**
```
ARGS_NAMES: {"id":"123","name":"test"}   ← entire blob as one key
ARGS values: empty

Workaround

Add the following rules to your inline Coraza configuration, before CRS rules load:

apache

SecRule REQUEST_HEADERS:Content-Type "@rx application/json" \
  "id:900,phase:1,pass,nolog,ctl:requestBodyProcessor=JSON"

SecRule REQUEST_HEADERS:Content-Type "@rx (?:application(?:/soap\+|/)|text/)xml" \
  "id:901,phase:1,pass,nolog,ctl:requestBodyProcessor=XML"

Suggested Fix

Ship these two rules as part of the CrowdSec AppSec default configuration, equivalent to what the NGINX ModSecurity connector provided out of the box. This would ensure JSON and XML body inspection works correctly without requiring manual intervention from users. Thanks.

Hi,

I think I found the reason:

In fact, CrowdSec has these processing rules out of the box - base-config WAF Rule , but the trick is that they work only with in-band CRS rules(we have out-of-band CRS collection installed). Look at lines 3-4 of appsec-default WAF Configuration :

inband_rules:
 - crowdsecurity/base-config 

When I added the same line to the out-of-band block, it started to parse JSON correctly:

outofband_rules:
 - crowdsecurity/base-config 

Is it a misconfiguration from our end, or is it by design like that?