
str.lower() pode virar vulnerabilidade de segurança em Python
Nomes de domínio além do ASCII
Alguns padrões da internet aceitam apenas caracteres ASCII, mas o mundo escreve com muito mais que o alfabeto latino - por isso existe um mapeamento de Unicode para ASCII aplicado a nomes de domínio. Parte dessa solução é o NamePrep, definido na RFC 3491 como perfil do StringPrep (RFC 3454) e peça central do esquema conhecido como IDNA 2003 (Internationalizing Domain Names in Applications). O IDNA 2003 foi depois substituído pelo IDNA 2008, especificado nas RFCs 5890, 5891, 5892 e 5893.
O Python convive com as duas versões: o IDNA 2003 fica embutido no codec idna, acessível por str.encode('idna'), enquanto o IDNA 2008 é coberto pelo pacote idna publicado no Python Package Index. Em geral, a recomendação é usar o pacote, mas às vezes o comportamento antigo é necessário. A implementação do StringPrep fica no módulo stringprep da biblioteca padrão.
A etapa de case folding e onde a falha entra
O StringPrep define, em sua seção 3.2, uma etapa de case folding - aproximadamente, como transformar um ponto de código em minúscula ou maiúscula - para permitir comparações sem distinção de caixa, mapeando caracteres pelas tabelas B.2 e B.3. A B.2 funciona como um equivalente ao str.lower(), convertendo tudo para minúsculas segundo as regras do Unicode, e a B.3 contém as exceções. O código do Python consulta primeiro as exceções da B.3 e, não encontrando correspondência, devolve o próprio code.lower().
À primeira vista isso parece correto - e é justamente a chamada a str.lower() que constitui a vulnerabilidade.
Por que minúsculas viram risco
O método str.lower() depende dos dados Unicode embarcados no interpretador em execução; a versão pode ser consultada em unicodedata.unidata_version (17.0.0 no exemplo do texto). O StringPrep, porém, exige consistência com uma versão específica, o Unicode 3.2.0: toda edição do Python ainda hoje distribui o banco de dados unicodedata.ucd_3_2_0 justamente para os algoritmos StringPrep e IDNA. As tabelas B.2 e B.3 da RFC 3454 são, na prática, as regras de case folding do Unicode 3.2.0 codificadas em formato de tabela. Quando str.lower() aplica regras de outra versão, abre-se uma diferença entre implementação e especificação - e isso caracteriza a vulnerabilidade.
A prova aparece num exemplo com o caractere Cherokee U+13A0: conforme a RFC 3454, codificar ᎠᎠ com o codec idna produz xn--58da; usando as regras de minúsculas do Unicode 17.0.0, o resultado passa a ser xn--kz9aa.
A correção
A solução foi criar novas exceções para que o str.lower() se comportasse, apenas nessa função, como se estivesse usando o Unicode 3.2.0. Para isso, foram percorridos todos os pontos de código do Unicode, registrando os casos em que o resultado difere entre a versão carregada pelo interpretador e a 3.2.0. Com isso, o IDNA 2003 volta a ficar consistente com a especificação.
A falha foi reportada por Bitshift, teve a correção desenvolvida em conjunto com Stan Ulbrych e revisada por Marc-Andre Lemburg e Petr Viktorin, e está catalogada como CVE-2026-17084. O trabalho do autor como Security Developer-in-Residence na Python Software Foundation recebe patrocínio da Alpha-Omega.