11 pontos por nuremberg 15 일 전 | 4 comentários | Compartilhar no WhatsApp

Resumo geral em uma linha

Uma vulnerabilidade de exaustão de CPU pré-autenticação ocorre no MultiPartParser do Django quando o corpo de uma parte com Content-Transfer-Encoding: base64 é composto majoritariamente por espaços em branco, fazendo com que uma única requisição de cerca de 2,5 MB provoque um tempo de processamento mais de 2.100 vezes maior que o normal (CVE-2026-33033)

Resumo

  • Pode ser acionada sem autenticação, mesmo em servidores com configuração padrão
    • Como o middleware de CSRF acessa request.POST antes de a view ser executada, o MultiPartParser é disparado automaticamente; por isso, até endpoints autenticados já gastam vários segundos na etapa de validação de CSRF
  • Uma única requisição de 20 MB pode ocupar um worker por cerca de 1 minuto
    • Em uma configuração comum do gunicorn com 4 a 16 workers, apenas algumas dezenas de requisições simultâneas já podem praticamente paralisar o servidor
  • O Django processa requisições multipart/form-data com o MultiPartParser, e como o middleware de CSRF acessa request.POST antes de entrar na view, esse parser sempre é executado mesmo sem autenticação
  • O núcleo da vulnerabilidade está em uma estrutura em que três camadas se multiplicam
    • (Layer 1) while-loop de alinhamento do base64: ao remover os espaços em branco do chunk, o estado remaining != 0 é mantido, então field_stream.read(1) passa a ser chamado repetidamente para todo o restante do stream
    • (Layer 2) custo oculto O(C) de LazyStream.read(1): a cada chamada de read(1), internamente um buffer de ~64 KB é retirado por inteiro e depois 65.535 bytes são empurrados de volta com unget(); esse padrão se repete continuamente
    • (Layer 3) concatenação de bytes O(C) em unget(): um novo objeto é criado toda vez com bytes + self._leftover
  • Uma única requisição de 2,5 MB provoca internamente cerca de 86 GB de cópias de memória, ocupando completamente um worker por cerca de 5,3 segundos em um M2. Com 20 MB, leva cerca de 1 minuto
  • Já existia um código de sanity check dentro de unget() (_update_unget_history), mas neste ataque o tamanho do unget() segue um padrão monotonicamente decrescente, diminuindo 1 a cada chamada, então a condição de detecção (number_equal > 40) nunca é satisfeita
  • O ponto central do patch da equipe do Django foi mudar read(4 - remaining) para read(self._chunk_size), passando a ler 64 KB de cada vez em vez de 1 a 3 bytes. Com isso, o número de chamadas de leitura cai de 2,5 milhões para cerca de 40
  • O valor padrão de client_max_body_size no Nginx é 1 MB, mas ele costuma ser relaxado em endpoints de upload de arquivos; já o valor padrão de LimitRequestBody no Apache httpd é 1 GB, então a defesa apenas com proxy não é garantida
  • A vulnerabilidade foi descoberta com o uso de Claude Code + Codex, e chama atenção o fato de um framework refinado ao longo de quase 20 anos ainda ter mantido um DoS pré-autenticação

4 comentários

 
kalista22 14 일 전

Vamosss

 
tangokorea 14 일 전

Alguém aqui já testou isso na prática?

 
nuremberg 14 일 전

Um PoC criado para demonstração foi publicado no GitHub.

https://github.com/ch4n3-yoon/CVE-2026-33033-PoC

 
tangokorea 14 일 전

Muito bom.