Programação Por Equipe DevBras

Capa do artigo: Falha crítica no Pillow (Python): como checar e corrigir

Falha crítica no Pillow (Python): como checar e corrigir

Falha crítica no Pillow permite rodar comando com uma imagem forjada. Veja como checar sua versão, atualizar para a 13.0.0 e se proteger no Docker e na distro.

Se a sua aplicação em Python recebe imagem de usuário, seja foto de perfil, anexo, upload para gerar miniatura ou um pipeline de documentos, este aviso é para você. O Pillow, a biblioteca de imagens mais usada do Python, lançou em 16/10/2026 a versão 13.0.0 com dezenas de correções de segurança. Entre elas estão falhas classificadas como críticas, e numa delas basta um Image.open() num arquivo forjado para executar comando no servidor.

Resumindo: atualize para o Pillow 13.0.0 (que exige Python 3.11 ou mais novo), reconstrua as imagens Docker, reinicie os workers e, enquanto não der, restrinja os formatos aceitos e tire o Ghostscript das máquinas que processam upload. Abaixo estão os comandos para checar e o que fazer se a sua distro ainda não tem pacote corrigido.

O que aconteceu com o Pillow

As notas oficiais do Pillow 13.0.0 trazem uma seção de segurança separada por gravidade (crítica, alta, média e baixa). Os avisos detalhados foram publicados no GitHub nos dias seguintes. Três deles chamam mais atenção:

CVE-2026-112398: arquivo FlashPix que executa comando

O aviso GHSA-5x8w-8jj5-q52h descreve um estouro de memória (heap overflow) no decodificador de blocos JPEG do formato FlashPix (FPX). Ele recebeu nota 9.2 (crítica) no CVSS 4.0 e afeta o Pillow até a 12.3.0.

  • O pesquisador mostrou uma prova de conceito em que abrir um único arquivo forjado com Image.open() executa um comando escolhido pelo atacante. O teste foi feito num container Ubuntu 24.04 com Pillow 12.2.0 e o ASLR desligado para simplificar.
  • O Pillow reconhece o arquivo pelo conteúdo (a assinatura OLE2), e não pela extensão. Chamar o arquivo de avatar.png não muda nada.
  • A falha só é alcançável quando o pacote opcional olefile está instalado, porque é ele que habilita o leitor de FPX.

CVE-2026-112900: nome de arquivo EPS que vira argumento do Ghostscript

Para abrir EPS, o Pillow chama o programa gs (Ghostscript). O aviso GHSA-w889-685w-c8fh mostra que o nome do arquivo era passado ao Ghostscript sem tratamento. Um nome que começa com - vira opção do comando, e um nome que começa com @ faz o Ghostscript ler argumentos de outro arquivo. A execução de comando exige que o atacante controle o nome e também um arquivo de texto referenciado.

O GitHub deu nota 9.2, mas os mantenedores classificam como média nas notas da versão, justamente porque o atacante precisa controlar o nome do arquivo. Afeta da 2.6.0 até a 12.3.0, e só importa onde o gs está instalado.

CVE-2026-112393: TIFF salvo em memória

Segundo o registro no tracker do Debian e o aviso GHSA-332p-7hrc-92wf, salvar um TIFF comprimido num destino sem descritor de arquivo, como io.BytesIO, pode estourar memória e vazar pedaços da memória do processo no arquivo gerado. O bug existe desde a 2.0.0. Há prova de conceito pública que derruba o processo. Salvar em arquivo de verdade no disco não é afetado.

Alguém está explorando?

Até 24/10/2026, não há exploração conhecida de nenhuma dessas falhas, segundo o The Hacker News. Mas a correção e as provas de conceito são públicas. Não vale esperar.

Quem está exposto

O risco real está onde o Pillow abre arquivo que veio de fora. Os casos mais comuns são:

  • Upload de imagem em Django, Flask ou FastAPI: foto de perfil, anexo de chamado, imagem de produto.
  • Geração de miniaturas (thumbnail()), redimensionamento e conversão de formato logo depois do upload.
  • Workers em segundo plano, como tarefas do Celery, RQ ou processos do gunicorn que recebem o arquivo e processam sem ninguém olhar.
  • Pipelines de documentos, OCR e projetos de machine learning que leem imagens de terceiros.

Se o Pillow só abre imagens geradas por você mesmo, o risco é bem menor. Mesmo assim, atualize.

Como checar se você está vulnerável

No servidor ou no virtualenv

Qualquer versão abaixo da 13.0.0 é vulnerável. Rode no mesmo ambiente em que a aplicação roda:

python -c "import PIL; print(PIL.__version__)"
pip show pillow
pip show olefile   # se aparecer, o leitor de FlashPix está ativo
which gs           # se mostrar um caminho, o EPS passa pelo Ghostscript

Confira também o que o projeto declara: requirements.txt, poetry.lock, uv.lock ou Pipfile.lock. Ele costuma entrar como dependência de outra biblioteca, então procure no lock também.

Dentro dos containers

O que vale é a versão que está rodando. Container por container:

docker ps --format '{{.Names}}'
docker exec NOME_DO_CONTAINER pip show pillow
docker exec NOME_DO_CONTAINER sh -c 'pip show olefile; which gs'

Para ver a idade das imagens no host, use docker image ls. Imagem construída antes de 16/10/2026 não tem como ter o Pillow 13. Se você tem muitos hosts e stacks, um painel ajuda a não esquecer nenhum. No Dockyard, o painel Docker da DevBras, dá para ver numa tela quais containers estão no ar em cada servidor e recriar a stack depois do rebuild.

Como corrigir

1. Atualize o Pillow

pip install -U "Pillow>=13.0.0"

Depois, atualize o lock (por exemplo poetry update pillow ou uv lock --upgrade-package pillow) e faça o commit, senão o próximo build pode trazer a versão antiga de volta. Escrevemos sobre isso no post dependência com cadeado.

2. Reconstrua as imagens Docker

Atualizar no seu computador não muda nada em produção. Reconstrua a imagem sem cache para garantir que o pip install rode de novo, e recrie os containers:

docker compose build --no-cache
docker compose up -d --force-recreate

Aproveite para conferir se a imagem base está fixada numa versão, em vez de depender de uma tag como latest, que muda sozinha.

3. Reinicie os workers

O Python carrega a biblioteca quando o processo sobe. Um worker do Celery ou do gunicorn que está no ar há dias continua com o Pillow antigo na memória, mesmo depois do pip install. Reinicie todos os processos que importam o Pillow e confirme a versão de novo dentro do container.

4. Python 3.10 ou mais antigo

O Pillow 13 deixou de suportar o Python 3.10, que chegou ao fim da vida em outubro de 2026. O pacote no PyPI exige Python 3.11 ou mais novo. Se o pip se recusar a instalar a 13.0.0, a correção passa por atualizar o Python (ou a imagem base). Enquanto isso não acontece, aplique as mitigações da próxima seção.

5. Pillow da distribuição (python3-pil)

Quem instalou o Pillow pelo apt ainda não tem pacote corrigido. No tracker de segurança do Debian, o Debian 12 (bookworm, 9.4.0) e o 13 (trixie, 11.1.0) aparecem como vulneráveis, e só o sid já tem a 13.0.0. Na página do Ubuntu, todas as versões estão como Needs evaluation. Até sair a atualização, as opções são rodar a aplicação num virtualenv com o Pillow do PyPI ou aplicar as mitigações abaixo.

Mitigação temporária enquanto não atualiza

As medidas abaixo estão no guia de segurança do Pillow e nos avisos:

  • Tire o Ghostscript das máquinas e imagens que processam upload. Sem gs, o EPS não chega ao Ghostscript.
  • Nunca passe ao Image.open() um nome de arquivo escolhido pelo usuário. Grave o upload com um nome gerado por você (um UUID, por exemplo) ou passe o arquivo já aberto.
  • Miniatura de JPEG não confiável: há uma falha parecida no JPEG sem perdas (CVE-2026-112403). Os avisos recomendam evitar draft() e thumbnail() nesses arquivos, chamar load() antes ou usar reducing_gap=None.
  • TIFF em memória: trate como caminho sensível salvar em BytesIO um TIFF que veio do usuário.
  • Restrinja os formatos aceitos com o parâmetro formats do Image.open(), como o próprio guia recomenda.

Na prática, um upload de foto de perfil fica assim (testado com o Pillow 13.0.0):

import uuid
from PIL import Image, UnidentifiedImageError

FORMATOS_PERMITIDOS = ["JPEG", "PNG", "WEBP"]

def abrir_upload(arquivo):
    """Abre a imagem enviada aceitando só os formatos esperados."""
    try:
        img = Image.open(arquivo, formats=FORMATOS_PERMITIDOS)
        img.load()
        return img
    except UnidentifiedImageError:
        raise ValueError("Formato de imagem não aceito")

# nome gerado pelo servidor, nunca o nome que veio do usuário
nome_salvo = f"{uuid.uuid4()}.png"

No teste, um TIFF renomeado para foto.png foi recusado com essa lista, enquanto o Image.open() sem formats abriu o arquivo como TIFF normalmente.

Dicas nossas (não oficiais)

As duas medidas abaixo são deduções nossas a partir dos avisos, não recomendações oficiais. Elas não substituem a atualização.

  • Se você não precisa de FlashPix nem de MIC, desinstalar o olefile (pip uninstall olefile) tira o leitor de FPX do caminho. Antes, confira se nenhuma outra dependência usa esse pacote.
  • Uma lista em formats sem "FPX" e sem "EPS" impede que esses leitores sejam acionados pelo Image.open(). Ela não protege contra a falha da miniatura de JPEG, que usa um JPEG válido. O próprio aviso dessa falha avisa que formats=["JPEG"] não a evita.

Rodar o processamento de imagem num container separado e sem root também limita o estrago de qualquer falha desse tipo. Veja como fazer isso em o processo no container não precisa ser root.

O que procurar nos logs e nos arquivos

Vale uma conferida rápida, principalmente se a aplicação aceita upload público:

  • Arquivos com conteúdo de outro formato na pasta de uploads: assinatura OLE2 (FlashPix) ou PostScript (EPS) em arquivos com nome de imagem comum.
  • Nomes de arquivo começando com - ou @.
  • Processos gs filhos do seu worker quando a aplicação nem deveria aceitar EPS.
  • Workers morrendo do nada: falha de segmentação, sinais como SIGSEGV ou SIGABRT, mensagens de heap corrompido do glibc ou tarefas do Celery perdidas no meio do processamento de imagem.
  • Muitos UnidentifiedImageError ou DecompressionBombWarning, que o guia do Pillow recomenda registrar e monitorar.

Este comando lista arquivos cujo conteúdo começa com a assinatura OLE2 ou PostScript, seja qual for a extensão. Troque o caminho pela sua pasta de uploads:

find /caminho/uploads -type f -exec sh -c '
  h=$(head -c 8 "$1" | od -An -tx1 | tr -d " \n")
  case "$h" in
    d0cf11e0a1b11ae1) echo "OLE2/FlashPix? $1" ;;
    25215053*)        echo "PostScript/EPS? $1" ;;
  esac' _ {} \;

Se você usa o padrão de decodificar e regravar todo upload, como explicamos em upload de imagem seguro, lembre que essa regravação também passa pelo Pillow. Ela não protege de falhas no próprio decodificador.

Perguntas frequentes

Renomear ou validar a extensão do arquivo resolve?

Não. O Pillow identifica o formato pelo conteúdo. Um FlashPix chamado foto.png é lido como FlashPix. Para restringir, use o parâmetro formats e confira o tipo real do arquivo.

Não uso EPS nem FlashPix. Preciso atualizar?

Sim. A 13.0.0 corrige dezenas de falhas em vários formatos (TIFF, JPEG, PDF, GIF, BMP e outros), e quem decide o formato é quem manda o arquivo, não você.

Para quem já está nessa rotina: nesta mesma semana a falha do Brotli pediu os mesmos passos de rebuild e restart. O roteiro completo está em falha no Brotli: como checar e corrigir.

Avalie este artigo