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:
-
False positives — The entire JSON body becomes a single
ARGS_NAMESvalue, 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 -
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
-
Set up CrowdSec AppSec with Coraza + CRS protecting any application with a JSON API
-
Send a
POSTorPUTrequest withContent-Type: application/jsonand a JSON body -
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.