The OWASP Core Rule Set (CRS) is widely used in securing web applications. It offers a set of generic rules that are plugged into Web Application Firewalls (WAFs) such as ModSecurity to detect and block common web attacks like SQL injection, cross-site scripting (XSS), and more.
But even good security rules can have bugs, and today we’ll discuss a subtle yet important flaw: CVE-2026-21876, which affects how CRS handles multipart requests.
What is CVE-2026-21876 About?
The vulnerability lies in how CRS (before versions 4.22. and 3.3.8) processes multipart requests (think file uploads or rich form submissions).
The Bug
The current rule 922110 attempts to analyze every part of a multipart request header for malicious "charset" values, often used to trigger attacks via encoding.
When the rule is written with something like this
SecRule MULTIPART_PART_HEADERS "@rx charset\s*=\s*['\"]?([a-zA-Z-9_\-.]+)" \
"capture,chain,setvar:TX.charset_detected=%{TX.}"
...and is followed by chained rules that use the captured variables (TX:, TX:1), only the *last* matching value is available by the time the chain is completed.
If an attacker sends a multipart request like
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
...
------WebKitFormBoundary
Content-Disposition: form-data; name="file1"
Content-Type: text/plain; charset=evil
...malicious content...
------WebKitFormBoundary
Content-Disposition: form-data; name="file2"
Content-Type: text/plain; charset=utf-8
...benign content...
------WebKitFormBoundary--
CRS will iterate over every Content-Type header, but the capture variable (TX:) gets *overwritten* each time. If the last part has a safe charset (utf-8), the chained rules see _only_ that value, missing the evil charset in the first part.
Result: Malicious charsets in earlier parts are skipped if the attacker simply ends with a safe-looking part.
A simplified view of the CRS rule logic could look like this in ModSecurity syntax
# Rule that inspects each part's header for charset using regex capture
SecRule MULTIPART_PART_HEADERS "@rx charset\s*=\s*['\"]?([a-zA-Z-9_\-.]+)" \
"id:922110,phase:2,block,log,chain,capture"
SecRule TX: "@rx ^(utf-7|ucs-2|...)$" "t:lowercase,block,msg:'Blocked dangerous charset'"
In later parts, use a safe, expected charset (like utf-8).
- The final value (TX:) used by the chained rule is the last header, so the threat from the first part is ignored.
Real-life Impact:
An attacker could bypass WAF protection by hiding a dangerous part early. If an app processes each part independently, malicious uploads could sneak through undetected by ModSecurity rules.
What was fixed
- Updated CRS versions 4.22. and 3.3.8 ensure capture variables are not overwritten so earlier malicious values are not missed. The logic processes and checks all parts, not just the last one.
Official patch references
- CVE-2026-21876 on GitHub OWASP CRS
- CRS Release Notes 4.22.
- CRS Release Notes 3.3.8
TL;DR
- If you use ModSecurity + CRS (before 4.22. or 3.3.8), malicious encodings in multipart requests can bypass your WAF.
More Info
- OWASP CRS Project
- Detailed Vulnerability Disclosure
- ModSecurity Documentation
Timeline
Published on: 01/08/2026 13:55:37 UTC
Last modified on: 04/09/2026 16:16:26 UTC