---
title: 'Bypassing WAF via Charset Confusion: The Story of CVE-2026-21876 | DevSense'
description: 'Discover how CVE-2026-21876 allows attackers to bypass the OWASP ModSecurity Core Rule Set (CRS) using multipart charset confusion and variable overwriting in rule 922110.'
faq:
    - { question: 'What is the root cause of the CVE-2026-21876 vulnerability?', answer: 'The root cause is a variable overwriting issue in OWASP CRS rule 922110. During multipart request parsing, the WAF writes the Content-Type and charset of each part into the same transaction-scoped variable. Since validation against the whitelist occurs only after processing all parts, only the content-type of the last part is actually validated, leaving preceding parts unchecked.' }
    - { question: 'Why does charset confusion with IBM037 or UTF-7 lead to a WAF bypass?', answer: 'Attackers can encode their malicious payloads in non-standard charsets like IBM037 (an EBCDIC-based encoding) or UTF-7. The WAF does not decode or inspect these parts because their Content-Type validation is bypassed and the signature rules are written for ASCII/UTF-8. However, backend servers like Undertow or ASP.NET Core automatically support and decode these charsets, restoring the malicious payload to plain text on the backend.' }
    - { question: 'How does the OWASP Core Rule Set remediate this vulnerability?', answer: 'The vulnerability was patched by rewriting the ruleset. Instead of overwriting a single variable, the new logic initializes a counter, stores the Content-Type of each multipart part in a separate, uniquely named variable, and then iterates through all stored variables to ensure every single one is validated against the whitelist.' }
published: '2026-07-09'
---
# Bypassing WAF via Charset Confusion: The Story of CVE-2026-21876

Web Application Firewalls (WAFs) are critical components of modern web security. They sit between clients and backend servers, inspecting incoming traffic to detect and block malicious payloads such as SQL Injection (SQLi) and Cross-Site Scripting (XSS). However, a WAF is only as strong as its rules.

In this article, we analyze **CVE-2026-21876**, a high-severity vulnerability in the **OWASP ModSecurity Core Rule Set (CRS)**. This flaw allowed attackers to completely bypass security checks by exploiting a variable overwriting bug combined with automatic charset decoding on modern application backends.

---

## ModSecurity and the OWASP Core Rule Set (CRS)

**ModSecurity** is an open-source, signature-based WAF engine that integrates with popular web servers like Nginx, Apache, and IIS. Because ModSecurity is just an engine, it relies on rules to detect attacks. 

The **OWASP ModSecurity Core Rule Set (CRS)** is the industry-standard set of generic attack detection rules. It is widely deployed across major cloud providers and CDN networks, including Azure WAF, AWS WAF configurations, Cloudflare, Fastly, and Google Cloud Armor. With millions of active deployments globally, a vulnerability in the CRS ruleset has a cascading impact, exposing vast numbers of web infrastructures.

---

## The Mechanics of CVE-2026-21876

The vulnerability lies within rule **922110**, which was designed to enforce strict validation on the `Content-Type` headers of multipart request parts. Specifically, it ensures that every part in a `multipart/form-data` request uses a whitelisted content type and charset (e.g., standard text or boundary structures).

### The Variable Overwrite Bug

When a client submits a multipart request, ModSecurity iterates through each part of the payload. During this process, rule 922110 extracts the `Content-Type` of each part and stores it in a transaction-scoped (global) variable (for example, `TX:content_type`).

The bug is simple yet critical:
1. **The Loop:** ModSecurity iterates through all parts, extracting and writing the Content-Type of the current part into the same variable, overwriting the previous value.
2. **The Validation:** The validation check against the whitelist is executed only **after** the loop completes, rather than inside the loop for each individual part.
3. **The Consequence:** Only the Content-Type of the **very last part** of the multipart request is validated against the whitelist. Any prior parts are ignored by rule 922110.

If represented in Python-like pseudocode, the vulnerable logic looks like this:

```python
# Vulnerable Logic (CVE-2026-21876)
content_type_var = None

# Extracting content types in a loop
for part in request.multipart_parts:
    content_type_var = part.headers.get("Content-Type")  # Overwrites global variable

# Validation is performed outside the loop
if not is_whitelisted(content_type_var):
    block_request()
```

By adding a dummy parameter with a whitelisted Content-Type at the end of the request, an attacker can overwrite the global variable with a "safe" value, bypassing the check entirely.

---

## The Exploitation Vector: Charset Confusion

To exploit this, an attacker combines the variable overwrite bug with **Charset Confusion**.

WAF signature rules (such as those looking for `UNION SELECT` or `<script>`) are designed to analyze payloads encoded in standard charsets like UTF-8 or ASCII. If an attacker submits a payload encoded in a non-standard charset, the WAF will fail to match the signatures because the raw byte sequences do not look like malicious keywords.

Normally, rule 922110 would block any request containing an unapproved charset. However, because of the overwrite bug, the WAF allows unauthorized charsets in the initial parts as long as the last part is benign.

### The Attack Payload Construction

An attacker structures a `multipart/form-data` request with two parts:
1. **Part 1 (Malicious Payload):** Uses a non-standard charset like `IBM037` (an EBCDIC-based charset). The WAF bypasses the Content-Type check (due to the overwrite) and fails to inspect the payload because it is encoded in EBCDIC.
2. **Part 2 (Dummy Payload):** Uses a standard charset like `utf-8`. This overwrites the WAF's tracking variable with a valid value, allowing the entire request to pass.

### Raw HTTP Request Demonstrating the Bypass

```http
POST /vulnerable-endpoint HTTP/1.1
Host: target-app.com
Content-Length: 432
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySafeAndBypass

------WebKitFormBoundarySafeAndBypass
Content-Disposition: form-data; name="id"
Content-Type: text/plain; charset=ibm037

K@ñ%ÈÊÈÀñKñ
------WebKitFormBoundarySafeAndBypass
Content-Disposition: form-data; name="legitimate_param"
Content-Type: text/plain; charset=utf-8

safe-value
------WebKitFormBoundarySafeAndBypass--
```

> [!NOTE]
> In the example above, the value `K@ñ%ÈÊÈÀñKñ` represents the SQL injection payload `' OR 1=1 --` encoded in EBCDIC (IBM037). To the WAF, these bytes appear as gibberish and do not match any attack signatures.

---

## Backend Decoding Behavior

A WAF bypass is only dangerous if the backend application actually processes the malicious payload. 

Many modern backend servers and frameworks automatically parse `multipart/form-data` requests. When they encounter a part with a specific `charset` parameter in its `Content-Type` header, they automatically decode the bytes of that part using the specified charset.

Some notable backends showing this behavior out-of-the-box include:
- **Spring Boot running on Undertow:** Undertow automatically resolves and decodes multipart values based on the charset specified in each part's Content-Type.
- **ASP.NET Core:** The built-in multipart parser respects the charset parameter, automatically translating EBCDIC (IBM037) or UTF-7 strings back into UTF-8/Unicode strings before they reach application controllers.

When the request reaches the backend:
1. The backend parses Part 1, sees `charset=ibm037`, and decodes the EBCDIC bytes back into standard text: `' OR 1=1 --`.
2. The application executes the parameter, triggering the SQL injection or XSS vulnerability.
3. The WAF remains completely oblivious because it only validated the `charset=utf-8` of Part 2 and saw only EBCDIC gibberish in Part 1.

---

## Core Rule Set Remediation and Patch Details

Once the vulnerability was disclosed, the OWASP CRS team released a patch that restructured how rule 922110 handles multipart validation. Instead of a single global variable, the rule was split into three distinct phases:

1. **Counter Initialization:** Initializes a unique counter to keep track of multipart parts.
2. **Distinct Storage:** Extracts and writes the Content-Type and charset of *each* part into a distinct variable mapped to that part index (e.g., `TX:multipart_content_type_0`, `TX:multipart_content_type_1`, etc.).
3. **Comprehensive Validation:** Iterates through all recorded variables and validates each one individually against the whitelist. If any single part fails the check, the WAF blocks the request.

The patched logic behaves like the following corrected pseudocode:

```python
# Patched Logic
content_types = []

# Collect all Content-Types in a list
for part in request.multipart_parts:
    content_types.append(part.headers.get("Content-Type"))

# Validate every single part
for ct in content_types:
    if not is_whitelisted(ct):
        block_request()
        break
```

This patch successfully eliminates the possibility of variable overwriting, ensuring that charset confusion attacks are caught and blocked at the WAF level.