---
title: 'Обход WAF через путаницу кодировок: история уязвимости CVE-2026-21876 | DevSense'
description: 'Узнайте, как уязвимость CVE-2026-21876 позволяет обходить правила OWASP ModSecurity Core Rule Set (CRS) с помощью путаницы кодировок multipart-запросов и перезаписи переменных в правиле 922110.'
faq:
    - { question: 'Какова основная причина уязвимости CVE-2026-21876?', answer: 'Основная причина заключается в перезаписи переменных в правиле OWASP CRS 922110. При разборе multipart-запроса WAF записывает Content-Type и кодировку (charset) каждой части в одну и ту же глобальную переменную транзакции. Поскольку валидация по белому списку происходит только после обработки всех частей, проверяется только тип последней части, а предыдущие остаются без проверки.' }
    - { question: 'Почему путаница кодировок с использованием IBM037 или UTF-7 приводит к обходу WAF?', answer: 'Атакующие кодируют вредоносную полезную нагрузку в нестандартных кодировках (например, IBM037 на базе EBCDIC или UTF-7). WAF не декодирует и не проверяет такие части, так как проверка их Content-Type обходится из-за перезаписи переменных, а сигнатуры написаны для ASCII/UTF-8. Однако бэкенд-серверы (например, Undertow или ASP.NET Core) по умолчанию декодируют эти кодировки, восстанавливая исходный текст на бэкенде.' }
    - { question: 'Как эта уязвимость была исправлена в OWASP Core Rule Set?', answer: 'Уязвимость была устранена путем изменения логики правил. Вместо перезаписи одной переменной новая логика инициализирует счетчик, сохраняет Content-Type каждой части multipart-запроса в отдельную переменную с уникальным именем, а затем поочередно проверяет каждую сохраненную переменную по белому списку.' }
published: '2026-07-09'
---
# Обход WAF через путаницу кодировок: история уязвимости CVE-2026-21876

Брандмауэры веб-приложений (WAF) являются важнейшими компонентами современной веб-безопасности. Располагаясь между клиентом и бэкенд-сервером, они анализируют входящий трафик для обнаружения и блокирования вредоносных запросов, таких как SQL-инъекции (SQLi) или межсайтовый скриптинг (XSS). Однако эффективность WAF полностью зависит от надежности его правил.

В этой статье мы подробно разберем **CVE-2026-21876** — уязвимость высокой степени серьезности в **OWASP ModSecurity Core Rule Set (CRS)**. Этот недостаток позволял атакующим полностью обходить защитные механизмы WAF, используя баг перезаписи переменных в сочетании с автоматическим декодированием кодировок на бэкенде.

---

## ModSecurity и набор правил OWASP CRS

**ModSecurity** — это популярный сигнатурный движок WAF с открытым исходным кодом, который встраивается в веб-серверы Nginx, Apache и IIS. Сам по себе движок ModSecurity не защищает приложение — для работы ему необходим набор сигнатур.

**OWASP ModSecurity Core Rule Set (CRS)** — это де-факто стандарт правил обнаружения веб-атак. Он используется в Azure WAF, конфигурациях AWS WAF, Cloudflare, Fastly, Google Cloud Armor и многих других крупных платформах. Docker-образ CRS насчитывает более 5 миллионов скачиваний. Из-за такой распространенности уязвимость в CRS ставит под угрозу безопасность огромной части глобального веб-трафика.

---

## Механика уязвимости CVE-2026-21876

Проблема локализована в правиле с идентификатором **922110**. Это правило отвечает за проверку заголовков `Content-Type` у отдельных частей multipart-запроса (`multipart/form-data`) на соответствие белому списку допустимых типов контента и кодировок.

### Баг с перезаписью глобальной переменной

Когда клиент отправляет multipart-запрос, ModSecurity последовательно обходит каждую его часть. В ходе этого процесса правило 922110 извлекает значение `Content-Type` текущей части и сохраняет его в переменную транзакции (например, `TX:content_type`).

Проблема заключается в следующем:
1. **Цикл:** Движок проходит по всем частям запроса, извлекая `Content-Type` и записывая его в одну и ту же переменную, перезаписывая предыдущие значения.
2. **Проверка:** Валидация значения переменной по белому списку выполняется только **после** завершения цикла обработки всех частей.
3. **Результат:** Проверку проходит только тип контента **самой последней части** запроса. Предыдущие части правила игнорируют.

Схематично эту уязвимую логику можно выразить на Python-подобном псевдокоде:

```python
# Уязвимая логика (CVE-2026-21876)
content_type_var = None

# Извлечение заголовков в цикле
for part in request.multipart_parts:
    content_type_var = part.headers.get("Content-Type")  # Перезапись переменной

# Проверка выполняется вне цикла
if not is_whitelisted(content_type_var):
    block_request()
```

Добавив в самый конец multipart-запроса легитимный (даже неиспользуемый) параметр с разрешенным заголовком `Content-Type`, атакующий может обнулить результаты проверки всех предыдущих частей.

---

## Вектор атаки: Путаница кодировок (Charset Confusion)

Для реализации обхода баг с перезаписью переменной объединяется с техникой **путаницы кодировок**.

Сигнатуры WAF (например, для поиска ключевых слов `UNION SELECT` или `<script>`) настроены на анализ текста в стандартных кодировках вроде UTF-8 или ASCII. Если вредоносная полезная нагрузка закодирована в нестандартной кодировке, сигнатурный анализатор WAF не распознает в ней угрозу, так как байтовое представление символов меняется.

В обычном режиме правило 922110 заблокировало бы запрос с неизвестным WAF набором символов. Но из-за уязвимости перезаписи WAF пропускает любые кодировки в начале запроса, если последняя часть оформлена корректно.

### Структура запроса для обхода WAF

Запрос `multipart/form-data` формируется из двух частей:
1. **Часть 1 (Функциональный параметр с нагрузкой):** Указывается заголовок `Content-Type: text/plain; charset=ibm037` (устаревшая кодировка IBM037 на базе EBCDIC). Полезная нагрузка кодируется в IBM037. WAF пропускает этот тип контента из-за обхода правила 922110 и не детектирует атаку в теле части, так как не понимает кодировку EBCDIC.
2. **Часть 2 (Фиктивный параметр):** Используется разрешенная кодировка, например, `Content-Type: text/plain; charset=utf-8`. Она перезаписывает переменную WAF, и проверка проходит успешно.

### Пример сырого HTTP-запроса для обхода

```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]
> В данном примере строка `K@ñ%ÈÊÈÀñKñ` представляет собой SQL-инъекцию `' OR 1=1 --`, закодированную в кодировке IBM037. Для WAF этот блок выглядит как случайный набор бинарных данных, не содержащих признаков атаки.

---

## Поведение бэкенда при декодировании

Обход WAF опасен только в том случае, если бэкенд умеет обрабатывать и декодировать такую нагрузку.

Многие современные веб-серверы и фреймворки автоматически парсят multipart-запросы. При обнаружении параметра `charset` в заголовке `Content-Type` конкретной части бэкенд автоматически декодирует ее тело, используя указанную кодировку.

Такое поведение из коробки поддерживают:
- **Spring Boot на движке Undertow:** Undertow самостоятельно извлекает и декодирует значения multipart-параметров в соответствии с кодировкой, переданной в заголовке Content-Type этой части.
- **ASP.NET Core:** Встроенный обработчик multipart-запросов автоматически преобразует строки из кодировок вроде IBM037 или UTF-7 в стандартный Unicode до передачи параметров в контроллер приложения.

Когда запрос приходит на бэкенд:
1. Сервер обрабатывает Часть 1, видит кодировку `ibm037` и автоматически декодирует EBCDIC-байты обратно в читаемый текст: `' OR 1=1 --`.
2. Приложение обрабатывает этот параметр, запуская SQL-инъекцию.
3. WAF не фиксирует атаку, так как проверил только `charset=utf-8` у Части 2 и проигнорировал EBCDIC-содержимое Части 1.

---

## Исправление уязвимости в Core Rule Set

После выявления уязвимости команда разработчиков OWASP CRS выпустила исправление, переработав логику валидации правила 922110. Теперь вместо одной глобальной переменной используются три правила:

1. **Инициализация счетчика:** Запускается счетчик для отслеживания всех частей multipart-запроса.
2. **Раздельное хранение данных:** Заголовки `Content-Type` и `charset` каждой части сохраняются в отдельные переменные с уникальными именами (например, `TX:multipart_content_type_0`, `TX:multipart_content_type_1` и т.д.).
3. **Полная проверка:** WAF обходит все созданные переменные и сверяет каждую с белым списком. Если хотя бы у одной части тип контента или кодировка не разрешены, запрос блокируется.

Исправленная логика аналогична следующему коду:

```python
# Исправленная логика
content_types = []

# Сбор всех Content-Type в список
for part in request.multipart_parts:
    content_types.append(part.headers.get("Content-Type"))

# Валидация абсолютно всех элементов
for ct in content_types:
    if not is_whitelisted(ct):
        block_request()
        break
```

Данный патч полностью решает проблему перезаписи переменных, предотвращая возможность атак через путаницу кодировок на уровне WAF.