If you’re already running CrowdSec’s AppSec component with virtual patching rules, you’re well protected against known CVEs. But what about the attacks that don’t target a specific vulnerability: the SQL injections, cross-site scripting attempts, and command injections that probe your applications every day?
That’s where the OWASP Core Rule Set (CRS) comes in. CrowdSec supports CRS natively through the Coraza WAF engine, giving you broad attack pattern detection alongside your existing virtual patching setup.
In this post, we’ll walk through what CRS offers, how to deploy it, and how to tune it for your applications.
What is OWASP Core Rule Set (CRS)?
The OWASP Core Rule Set is among the most widely deployed sets of WAF rules worldwide. Actively maintained by the OWASP community, it provides generic attack detection against:
- SQL injection
- Cross-site scripting
- Command injection
- Path traversal
- And more
Unlike virtual patching, which targets specific known CVEs, the CRS uses a more generic approach to detect exploitation of still-unknown vulnerabilities.
This comes at a cost: a higher chance of false positives depending on the application.
Using both CRS and virtual patching rules is complementary: virtual patching rules for precise targeting of known vulnerabilities without the need for any configuration, and CRS to catch everything else.
Using the CRS with CrowdSec
CrowdSec offers two deployment modes for CRS: out-of-band or in-band.
Out-of-band (non-blocking)
In this mode, CRS rules analyze your traffic asynchronously. No request is ever blocked by a single rule match. Instead, events are generated and fed into CrowdSec’s scenario engine. If an IP triggers too many different rule violations in a short timespan, it gets banned.
This is the recommended starting point because it lets you:
- See what CRS detects in your traffic without disrupting legitimate users
- Identify false positives and tune the CRS accordingly
In-band (blocking)
In blocking mode, requests that reach the CRS threshold for blocking are dropped immediately. Alerts are generated by default, giving you visibility into what is blocked.
Getting Started: Installation
Prerequisites
You need a working CrowdSec setup with a WAF-capable remediation component. This includes the Nginx/OpenResty bouncer, Traefik bouncer, HAProxy bouncer, or any other bouncer (also known as Remediation Component) that supports the AppSec protocol.
Step 1: Install the CRS collection
For non-blocking mode:
cscli collections install crowdsecurity/appsec-crs
This installs:
crowdsecurity/crs: the configuration that loads CRS rules in out-of-band modecrowdsecurity/crowdsec-appsec-outofband: a scenario that bans IPs after 5+ out-of-band rule violations to offer a base level of protection, even if the rules themselves do not block anything directly.
Step 2: Configure the acquisition
Add the CRS config to your acquisition file (e.g., /etc/crowdsec/acquis.d/waf.yaml):
source: appsec
appsec-configs:
- crowdsecurity/crs
labels:
type: appsec
If you already have an acquisition file for AppSec with other configs (like virtual patching), simply add crowdsecurity/crs to the existing appsec-configs list.
Step 3: Enable per-match alerting
By default, non-blocking mode doesn’t generate an alert for every individual rule match. If you want visibility into each match (useful during the tuning phase), create /etc/crowdsec/appsec-configs/crs-alerting.yaml:
name: custom/crs-alerting
on_match:
- filter: IsOutBand == true
apply:
- SendAlert()
- CancelEvent()
The CancelEvent() directive is optional: if set, no event is generated for the scenario engine, meaning CrowdSec won’t take automated decisions based on these matches. This is useful if you want alerts for monitoring but don’t want any automated bans yet.
Then add it to your acquisition:
source: appsec
appsec-configs:
- crowdsecurity/crs
- custom/crs-alerting
labels:
type: appsec
Even though an alert is generated, because the rules are loaded out-of-band, nothing will be blocked, but you will still know exactly what is matching.
Step 4: Restart CrowdSec
sudo systemctl restart crowdsec
Step 5: Confirm it’s working
Now, we can see if everything is working as it should. Let’s exploit a (fake) SQL injection:
$ curl "localhost/?a='+OR+'1'='1" -i
HTTP/1.1 200 OK
...
Our request was not blocked: it’s expected as the CRS is configured in non-blocking mode.
If we look at the list of alerts in crowdsec with cscli alerts list and cscli alerts inspectd -d, We can indeed see the request was detected by the CRS:
root@instance-20240401-2335:~# cscli alerts inspect -d 105901
################################################################################################
- ID : 105901
- Date : 2026-03-12T09:44:32Z
- Machine : 717704f5186c4314928c2060bf5169f32E1tgFLtep049JcD
- Simulation : false
- Remediation : false
- Reason : anomaly score out-of-band: sql_injection: 10, anomaly: 10,
- Events Count : 4
- Scope:Value : Ip:127.0.0.1
- Country :
- AS :
- Begin : 2026-03-12T09:44:32Z
- End : 2026-03-12T09:44:32Z
- UUID : 8cf71673-81d7-4ba0-b211-0d415a0f51fd
- Context :
╭───────────────┬─────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼─────────────────────────────────────────────────────┤
│ ja4h │ ge11nn020000_cf69e1861692_000000000000_000000000000 │
│ matched_zones │ ARGS.a │
│ method │ GET │
│ msg │ SQL Injection Attack Detected via libinjection │
│ name │ native_rule:942100 │
│ target_uri │ /?a='+OR+'1'='1 │
│ user_agent │ curl/7.81.0 │
╰───────────────┴─────────────────────────────────────────────────────╯
- Events :
- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────┤
│ data │ │
│ matched_zones │ REQBODY_PROCESSOR │
│ message │ Enabling body inspection │
│ rule_name │ native_rule:901340 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────╯
- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────────┤
│ data │ Matched Data: s&sos found within ARGS:a: ' OR '1'='1 │
│ matched_zones │ ARGS.a,ARGS.a │
│ message │ SQL Injection Attack Detected via libinjection │
│ rule_name │ native_rule:942100 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────────╯
- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────┤
│ data │ │
│ matched_zones │ TX.blocking_inbound_anomaly_score │
│ message │ Inbound Anomaly Score Exceeded (Total Score: 10) │
│ rule_name │ native_rule:949110 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────╯
- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ data │ │
│ matched_zones │ UNKNOWN │
│ message │ Anomaly Scores: (Inbound Scores: blocking=10, detection=10, │
│ │ per_pl=10-0-0-0, threshold=5) - (Outbound Scores: │
│ │ blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - │
│ │ (SQLI=10, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0, │
│ │ SESS=0, COMBINED_SCORE=10) │
│ rule_name │ native_rule:980170 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────────────────╯
A note about the reason given for the block: CRS works with a threshold system. Each rule that matches on the request will increase by some value a global score, and if the global score exceeds a preconfigured threshold (5 by default), the request will be blocked.
This is why the reason appears as anomaly score out-of-band: sql_injection: 10, anomaly: 10,.
Crowdsec automatically extracts the scoring information from the CRS to give you, at first glance, details about what was wrong with the request.
Switching to blocking mode
Once you’re confident in your CRS tuning, switch to blocking mode:
cscli collections install crowdsecurity/appsec-crs-inband
Update your acquisition:
source: appsec
appsec-configs:
- crowdsecurity/crs-inband
labels:
type: appsec
In this mode, any request that reaches the default CRS threshold is dropped, and alerts are generated automatically.
Let’s retry the same request as previously:
$ curl "localhost/?a='+OR+'1'='1" -i
HTTP/1.1 403 Forbidden
...
This time, the request actually got blocked!
Again, we can inspect the alert:
root@instance-20240401-2335:~# cscli alerts inspect -d 105902
################################################################################################
- ID : 105902
- Date : 2026-03-12T09:49:04Z
- Machine : 717704f5186c4314928c2060bf5169f32E1tgFLtep049JcD
- Simulation : false
- Remediation : false
- Reason : anomaly score block: sql_injection: 10, anomaly: 10,
- Events Count : 4
- Scope:Value : Ip:127.0.0.1
- Country :
- AS :
- Begin : 2026-03-12T09:49:03Z
- End : 2026-03-12T09:49:03Z
- UUID : 7ef38529-5e7e-4d2e-9e21-24ec7a89a1ee
- Context :
╭───────────────┬─────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼─────────────────────────────────────────────────────┤
│ ja4h │ ge11nn020000_cf69e1861692_000000000000_000000000000 │
│ matched_zones │ ARGS.a │
│ method │ GET │
│ msg │ SQL Injection Attack Detected via libinjection │
│ name │ native_rule:942100 │
│ target_uri │ /?a='+OR+'1'='1 │
│ user_agent │ curl/7.81.0 │
╰───────────────┴─────────────────────────────────────────────────────╯
- Events :
- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────┤
│ data │ │
│ matched_zones │ REQBODY_PROCESSOR │
│ message │ Enabling body inspection │
│ rule_name │ native_rule:901340 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────╯
- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────────┤
│ data │ Matched Data: s&sos found within ARGS:a: ' OR '1'='1 │
│ matched_zones │ ARGS.a,ARGS.a │
│ message │ SQL Injection Attack Detected via libinjection │
│ rule_name │ native_rule:942100 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────────╯
- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────┤
│ data │ │
│ matched_zones │ TX.blocking_inbound_anomaly_score │
│ message │ Inbound Anomaly Score Exceeded (Total Score: 10) │
│ rule_name │ native_rule:949110 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────╯
- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────────────╮
│ Key │ Value │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ data │ │
│ matched_zones │ UNKNOWN │
│ message │ Anomaly Scores: (Inbound Scores: blocking=10, detection=10, │
│ │ per_pl=10-0-0-0, threshold=5) - (Outbound Scores: │
│ │ blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) - │
│ │ (SQLI=10, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0, │
│ │ SESS=0, COMBINED_SCORE=10) │
│ rule_name │ native_rule:980170 │
│ target_fqdn │ localhost │
│ uri │ /?a='+OR+'1'='1 │
╰───────────────┴──────────────────────────────────────────────────────────────╯
It’s almost the same, except for the reason: anomaly score block: sql_injection: 10, anomaly: 10,.
The presence of block means the request was actually blocked and never reached the target application.
Customizing CRS for Your Applications
Out of the box, CRS is tuned for broad coverage. For any moderately complex application, you’ll likely need some customization to eliminate false positives. CrowdSec uses the CRS plugin system for all customizations, keeping your changes separate from the CRS rules themselves.
CRS Plugins for Popular Applications
The CRS community maintains exclusion plugins for popular web applications. These plugins contain pre-built rule exclusions that prevent common false positives for each application.
The following plugins are available directly from the CrowdSec Hub:
- WordPress
- NextCloud
- PHPMyAdmin
- CPanel
- Drupal
- PHPBB
- DokuWiki
- Xenforo
Installing a plugin
cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-
For example, to install the WordPress plugin:
cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-wordpress
Once installed, the plugin loads automatically after a CrowdSec restart.
The plugins are updated automatically, like any other Hub items, so you will receive upstream changes automatically.
You can also manually deploy custom plugins; refer to our documentation.
Putting It All Together: Defense in Depth
Here’s how a complete AppSec configuration might look, combining virtual patching and CRS:
# /etc/crowdsec/acquis.d/waf.yaml
source: appsec
appsec-configs:
- crowdsecurity/appsec-default # Virtual patching (in-band)
- crowdsecurity/crs # OWASP CRS (out-of-band)
labels:
type: appsec
With this setup:
- Virtual patching rules run in-band, immediately blocking requests that match known CVE patterns
- CRS rules run out-of-band, detecting broad attack patterns and feeding events into CrowdSec’s scenario engine
- IPs that trigger multiple CRS violations get banned automatically through the scenario system
- CrowdSec’s threat intelligence adds another layer, sharing decisions across the community
Once you’ve tuned CRS and are confident in your configuration, you can switch to the in-band CRS collection for immediate blocking with the following configuration:
# /etc/crowdsec/acquis.d/waf.yaml
source: appsec
appsec-configs:
- crowdsecurity/appsec-default # Virtual patching (in-band)
- crowdsecurity/crs-inband # OWASP CRS (in-band)
labels:
type: appsec
Next Steps
Now that you have deployed CRS with CrowdSec, here are some more resources and tools you can use to better protect your web applications:
- Browse the CRS documentation for detailed reference
- Search the CrowdSec Hub for available CRS plugins
- Check out the CrowdSec WAF quickstart guides if you haven’t set up the AppSec component yet
- Join the CrowdSec community to share your CRS tuning experiences



