Hello everyone,
I would like to implement a WAF for some websites. As part of this process, I'm testing ModSecurity with OWASP CRS.
To measure the performance impact, I set up a simple test environment on a Debian Bookworm server with the following packages:
- nginx 1.22.1-9
- libmodsecurity3 3.0.9-1
- libnginx-mod-http-modsecurity 1.0.3-1
- modsecurity-crs 3.3.4-1
You can find my modsecurity.conf at the end of this message, this is a basic setup with logs disabled.
I don't have any CRS custom (and tx.paranoia_level=1).
In Nginx, ModSecurity is enabled in a vhost with:
```
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
```
In Debian, modsecurity_includes.conf is :
```
include modsecurity.conf
include /usr/share/modsecurity-crs/owasp-crs.load
```
And owasp-crs.load is :
```
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
Include /usr/share/modsecurity-crs/rules/*.conf
Include /etc/modsecurity/crs/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
```
For benchmarking, I used `h2load` with the following command :
`h2load -n 5000 -c 10 -t 1 -T 5 -m 10 -H 'Accept-Encoding: gzip,deflate' "
https://mywebsite.com/logo.svg?something=../../etc"`
Results:
- With CRS rules enabled: ~1400 to ~2400 requests per second (results fluctuated quite a lot betweek those numbers).
- With ModSecurity enabled but without CRS rules: ~3800 requests per second (very consistent).
- With ModSecurity disabled: ~3800 requests per second (very consistent too).
So, in my environment, performances are dropping from ~40% to ~60%, which surprised me.
Is this expected behavior, or are there optimizations or best practices I should consider ?
I also tested with the latest CRS v4 (4.19.0), and the results were similar to v3.
Any insights or recommendations would be greatly appreciated!
Thanks!
FYI :
- CPU: Intel(R) Xeon(R) CPU D-1531 @ 2.20GHz (12 cores),
- RAM: 32 GB,
- NGINX worker_processes: auto,
- NGINX worker_connections: 768.
My modsecurity.conf :
```
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
SecRequestBodyLimit 23068672
# 22MB
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
SecRequestBodyJsonDepthLimit 512
SecArgumentsLimit 1000
SecRule &ARGS "@ge 1000" \
"id:'200007', phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2"
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IP %{MULTIPART_INVALID_PART}, \
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
SecRule TX:/^MSC_/ "!@streq 0" \
"id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'"
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /var/cache/www/modsec/
SecDataDir /var/cache/www/modsec/
SecAuditEngine Off
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABCFHZ
SecAuditLogType Concurrent
SecAuditLog /var/log/nginx/modsec/audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
SecStatusEngine Off
```