---
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.