Guia rápido de boas práticas de segurança em Python
Frank Fischer
27 de setembro de 2021
0 minutos de leitura
Em 2019, a Snyk lançou seu primeiro guia rápido de Python. Desde então, muitos aspectos da segurança em Python mudaram. Com base no que aprendemos como empresa de segurança para desenvolvedores — além das boas práticas específicas para Python —, preparamos este guia atualizado para ajudar você a manter seu código Python seguro. Antes de continuar, quero agradecer especialmente a Chibo e Daniel pela ajuda com este guia rápido!
Baixe o Guia rápido de boas práticas de segurança em Python de 2021
Confira as dicas de segurança em Python que vamos explorar:
Sempre sanitize dados externos
Analise seu código
Tenha cuidado ao baixar pacotes
Revise as licenças das dependências
Não use a versão padrão do Python do sistema
Use o recurso de ambientes virtuais do Python
Defina
DEBUG = Falseem produçãoTenha cuidado com a formatação de strings
(De)serialize com muita cautela
Use anotações de tipo em Python
Uma observação rápida antes de começarmos: é importante destacar que os dados da Snyk sobre o ecossistema Python, assim como pesquisas acadêmicas, mostram que Python não é mais (nem menos) seguro do que outras linguagens amplamente usadas. Este guia é voltado especificamente para quem trabalha com Python. Recomendamos conferir todos os nossos outros guias rápidos de segurança para saber como se proteger em outros ecossistemas.
1. Sempre sanitize dados externos
Dados externos são um vetor de ataque para qualquer aplicação e podem ser usados em ataques de injeção, XSS ou negação de serviço (DoS). Uma regra geral para manter a segurança em Python é sempre sanitizar os dados (remover informações confidenciais) de fontes externas, sejam eles provenientes de um formulário preenchido por um usuário, da raspagem de um site ou de uma consulta a um banco de dados. Além disso, sanitize os dados assim que entrarem na aplicação para evitar o tratamento inseguro. Isso reduz o risco de sua aplicação manipular acidentalmente dados confidenciais não sanitizados.
Ao sanitizar, é sempre melhor verificar o que a entrada deveria conter do que tentar lidar com as exceções. Também recomendamos usar bibliotecas de sanitização bem mantidas. Veja duas opções:
schema é “uma biblioteca para validar estruturas de dados Python, como as obtidas de arquivos de configuração, formulários, serviços externos ou análise da linha de comando, convertidas de JSON/YAML (ou outro formato) para tipos de dados Python”.
bleach é “uma biblioteca de sanitização de HTML baseada em uma lista de permissões que escapa ou remove marcações e atributos”.
Os principais frameworks vêm com funções próprias de sanitização, como flask.escape() do Flask ou django.utils.html.escape() do Django. O objetivo dessas funções é proteger contra entradas HTML potencialmente maliciosas, como:
A limitação dessa abordagem é que as bibliotecas não servem para tudo. Elas são especializadas em seus respectivos domínios. Vale lembrar: leia até o fim deste post para saber mais sobre como trabalhar com outros formatos de dados, como XML, que também podem conter dados maliciosos.
Outra opção bastante usada é deixar a renderização de HTML a cargo de mecanismos de templates, como o Jinja. Ele oferece vários recursos, entre eles o escape automático para evitar XSS usando o MarkupSafe.
Outro aspecto da sanitização é impedir que os dados sejam usados como comandos. Um exemplo típico é a injeção de SQL. Em vez de concatenar strings e variáveis para gerar uma consulta SQL, é recomendável usar parâmetros nomeados para indicar ao banco de dados o que deve ser tratado como comando e o que deve ser tratado como dado.
Melhor ainda: use um mapeador objeto-relacional (ORM), como o sqlalchemy, que deixaria a consulta do exemplo assim:
Assim, você tem um código mais legível, além de otimizações de ORM, como o cache, e ainda mais segurança e desempenho!
Para saber mais, confira nosso guia rápido de injeção de SQL.
2. Analise seu código
Desenvolvedores têm à disposição uma grande variedade de ferramentas de análise estática de código para manter a segurança em Python. Vamos conhecer três níveis diferentes de ferramentas.
Primeiro, o nível de linter. Há décadas, o PEP8 serve como guia de estilo para Python. Várias ferramentas estão disponíveis (e integradas aos IDEs) para verificar a conformidade com esse guia, como pep8, pylint, flake8 e outras.
Em seguida, ferramentas como o bandit transformam o código em uma árvore sintática abstrata (AST) e fazem consultas nela para encontrar problemas de segurança comuns. Esse nível vai além do que os linters típicos fazem, pois eles trabalham no nível sintático. Ainda assim, o bandit tem limitações por causa de sua representação intermediária e do desempenho. Por exemplo, ele não detecta problemas relacionados ao fluxo de dados (conhecidos como análise de taint), que podem causar falhas graves, como injeções de SQL ou XSS.
Por fim, ferramentas de teste de segurança de aplicações estático (SAST), como o Snyk Code, fazem análise semântica e consideram até mesmo problemas complexos entre arquivos. Diferentemente de outras ferramentas desse nível, o Snyk Code foi pensado para desenvolvedores: analisa o código rapidamente e se integra ao IDE (ou diretamente à linha de comando). O Snyk Code explica os resultados altamente precisos e oferece ajuda, incluindo exemplos para corrigir problemas de segurança em Python. E o melhor: é fácil começar a usar e gratuito para projetos open source (além de uma quantidade limitada de testes em projetos não OSS).
3. Tenha cuidado ao baixar pacotes
É fácil instalar pacotes, mas eles também podem introduzir vulnerabilidades de segurança em Python. Em geral, desenvolvedores usam o instalador padrão de pacotes do Python (pip), que utiliza o Python Package Index (PyPI). Por isso, é importante entender como os pacotes são adicionados ao PyPI.
O PyPI tem um processo para relatar problemas de segurança. Se alguém denunciar um pacote malicioso ou um problema no PyPI, a questão será tratada. Porém, os pacotes adicionados ao PyPI não passam por uma revisão — esperar isso dos voluntários que mantêm o PyPI seria irrealista.
Por isso, é prudente presumir que há pacotes maliciosos no PyPI e agir de acordo. Entre as medidas razoáveis estão pesquisar um pouco sobre o pacote que você quer instalar e conferir cuidadosamente a grafia do nome (um pacote com um nome que imita um erro de digitação comum de um pacote popular pode executar código malicioso). Antes de baixar um pacote, verifique-o no Snyk Advisor.

Uma busca rápida por um pacote no Snyk Advisor mostra muitas informações sobre ele, seu suporte na comunidade, o histórico de bugs e correções, entre outros dados. O Snyk Advisor também mostra o comando de instalação no topo da página de resultados. Como boa prática, copie e cole a grafia do nome para evitar typosquatting. O Snyk Advisor ajuda você a decidir se deve confiar em um pacote. Você pode consultar o histórico de problemas de segurança e quanto tempo levou para corrigi-los.
Outra boa prática é usar ambientes virtuais para isolar os projetos uns dos outros. Além disso, use pip freeze ou um comando equivalente para registrar as mudanças no ambiente na lista de requisitos.
Para manter as referências atualizadas, o Snyk Open Source usa um banco de dados de vulnerabilidades de ponta, que registra problemas de segurança e possíveis correções. O Snyk Open Source analisa os requisitos e fornece informações práticas sobre vulnerabilidades encontradas em dependências diretas e transitivas, ajudando você a corrigi-las na hora.
4. Revise as licenças das dependências
Ao considerar o uso de um projeto open source, é importante entender os termos de licenciamento. Projetos open source são gratuitos e estão disponíveis para uso, mas ainda podem estar sujeitos a termos e condições. Em geral, esses termos especificam como o software pode ser usado, se você precisa disponibilizar publicamente as alterações feitas e outros requisitos semelhantes. Conheça as licenças open source aplicáveis aos projetos que você usa para evitar riscos jurídicos.
Se o projeto adotar uma licença mais restritiva do que você esperava (GPL, SSPL etc.), você pode acabar numa situação difícil, tendo que cumprir os termos da licença ou parar de usar o projeto. Além disso, se precisar fazer alterações em um projeto sem licença, você pode violar a legislação de direitos autorais.
Para garantir a sustentabilidade do seu projeto e evitar riscos legais e de segurança desnecessários em Python, analise e corrija problemas de licenças e vulnerabilidades nas dependências do projeto.
O Snyk Open Source pode ajudar você a gerenciar a conformidade com licenças open source. Ele oferece uma forma simples para desenvolvedores terem visibilidade de ponta a ponta e, ao mesmo tempo, permite uma governança flexível.
5. Não use a versão padrão do Python do sistema
A maioria dos sistemas POSIX já vem com uma versão do Python instalada. O problema é que, em geral, as distribuições de Python incluídas no sistema não estão atualizadas.
Por isso, use a versão mais recente do Python disponível para seu sistema e os contêineres oficiais criados para executar Python, mantendo tudo atualizado. A Snyk pode ajudar. Analise seus contêineres para identificar as atualizações necessárias com o Snyk Container e verifique suas dependências com o Snyk Open Source.
6. Use ambientes virtuais do Python
O Python permite separar o desenvolvimento de aplicações em ambientes virtuais. Um ambiente virtual isola o interpretador Python, as bibliotecas e os scripts instalados nele. Isso significa que, em vez de usar uma versão global do Python e dependências globais para todos os seus projetos, você pode ter ambientes virtuais específicos para cada projeto, com suas próprias versões do Python e das dependências!
A maioria dos IDEs, CLIs e painéis, como o Anaconda Navigator, tem funções integradas para alternar entre ambientes virtuais.
Dica profissional: a partir da versão 3.5 do Python, recomenda-se usar venv; na versão 3.6, pyvenv foi descontinuado.
Os ambientes virtuais facilitam o desenvolvimento, o empacotamento e a distribuição de aplicações Python seguras. É altamente recomendável usá-los. Consulte a documentação do Python sobre venv para saber mais.
7. Defina DEBUG = False em produção
Em um ambiente de desenvolvimento, faz sentido ter mensagens de erro detalhadas. Já em produção, você deve evitar o vazamento de informações que possam ajudar um invasor a descobrir detalhes do seu ambiente, das bibliotecas ou do código.
Por padrão, a maioria dos frameworks vem com a depuração ativada. No Django, por exemplo, ela fica habilitada em settings.py. Em produção, defina a depuração como False para impedir que informações confidenciais da aplicação sejam expostas a invasores.
Dica profissional: ao fazer uma implantação em produção, é útil configurar seu sistema de implantação contínua para verificar, após a implantação, se essa opção está desativada.
8. Tenha cuidado com a formatação de strings
Apesar da ideia do Python de haver uma — e apenas uma — maneira de fazer as coisas, a linguagem oferece quatro maneiras diferentes de formatar strings (três nas versões anteriores ao Python 3.6).
A formatação de strings se tornou cada vez mais flexível e poderosa (f-strings são particularmente interessantes), mas, quanto maior a flexibilidade, maior o potencial de exploração. Por isso, quem usa Python deve pensar com cuidado em como formatar strings com entradas fornecidas pelos usuários.
O Python tem um módulo integrado chamado string. Ele inclui a classe Template, usada para criar strings de template.
Veja o exemplo a seguir.
Com o código acima, a variável greeting é avaliada como: “Olá, mundo, meu nome é Hayley.”
Esse formato de string é um pouco trabalhoso porque exige uma instrução de importação e oferece menos flexibilidade com tipos. Ele também não avalia instruções Python como as f-strings. Essas limitações fazem das strings de template uma ótima opção ao lidar com entradas de usuários.
Mais uma observação rápida sobre formatação de strings: tenha muito cuidado com SQL bruto, como mencionado acima.
9. (Des)serialize com muito cuidado
O Python oferece um mecanismo integrado para serializar e desserializar objetos Python, chamado “pickling”, por meio do módulo pickle. Sabe-se que ele não é seguro, por isso é recomendável usá-lo com muito cuidado e somente com fontes de dados confiáveis.
O novo padrão de fato para serialização e desserialização é YAML. O pacote PyYAML oferece um mecanismo para serializar tipos de dados personalizados em YAML e vice-versa. Mas o PyYAML está repleto de possíveis vetores de ataque. Uma maneira simples, mas eficaz, de proteger o uso do PyYAML é usar yaml.SafeLoader() em vez de yaml.Loader() como carregador.
Isso impede o carregamento de classes personalizadas, mas dá suporte a tipos padrão, como hashes e arrays.
Outro caso de uso comum é XML. Muitas vezes, usamos bibliotecas padrão, mas elas são vulneráveis a ataques comuns — como ataques de negação de serviço (DoS) ou expansão de entidades externas (uma fonte externa é referenciada). Uma boa primeira linha de defesa é o pacote defusedxml. Ele oferece proteções contra esses problemas comuns de segurança em XML.
Dica extra, sem relação com segurança: use anotações de tipo no Python
Na versão 3.5, foram introduzidas as dicas de tipo. Embora o ambiente de execução do Python não imponha as anotações de tipo, ferramentas como verificadores de tipos, IDEs, linters, ferramentas de SAST e outras podem se beneficiar de declarações mais explícitas por parte de quem desenvolve. Veja um exemplo que ilustra essa ideia:
Literal[...] foi introduzido na versão 3.8 e não é imposto pelo ambiente de execução (no nosso exemplo, você pode passar qualquer string), mas os verificadores de tipos podem identificar que o parâmetro está fora do conjunto permitido e emitir um aviso. Essa é uma funcionalidade muito útil, não apenas para a segurança no Python.
Observação: Como não são impostas pelo ambiente de execução, as dicas de tipo têm uso limitado para fins de segurança.
Comece a participar de desafios de Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.



