Segurança em Python
LidoEsta aula aborda os principais aspectos de segurança em Python, incluindo validação de entrada, gerenciamento de segredos, auditoria de dependências com pip-audit e os riscos de usar eval e pickle. O conteúdo é prático e voltado para desenvolvedores intermediários, com exemplos de código e boas práticas para escrever código Python mais seguro.
Segurança é um aspecto fundamental do desenvolvimento de software, e em Python não é diferente. Embora a linguagem ofereça muitas facilidades, também apresenta riscos específicos que precisam ser compreendidos e mitigados. Nesta aula, vamos explorar quatro áreas críticas: validação de entrada, gerenciamento de segredos, auditoria de dependências e os perigos de funções como eval e pickle.
Esses tópicos são essenciais para qualquer desenvolvedor que deseja criar aplicações robustas, evitar vulnerabilidades comuns e proteger dados sensíveis. Ao final, você terá um conjunto de práticas recomendadas e ferramentas que podem ser aplicadas imediatamente em seus projetos.
Validação de entrada
Validação de entrada é o processo de garantir que os dados fornecidos por usuários ou sistemas externos estão de acordo com o esperado antes de serem processados. Entrada não validada é uma das principais causas de vulnerabilidades como injeção de SQL, cross-site scripting (XSS) e estouro de buffer. Em Python, a validação deve ser feita de forma explícita e rigorosa.
Existem várias abordagens para validar dados. Uma delas é usar funções nativas como isinstance e str.isdigit para verificações simples. Para casos mais complexos, bibliotecas como pydantic ou marshmallow oferecem esquemas de validação declarativos. O princípio é: nunca confie nos dados de entrada, sempre valide e sanitize antes de usar.
# Exemplo de validação simples
def process_age(age_str):
if not age_str.isdigit():
raise ValueError("Idade deve ser um número inteiro")
age = int(age_str)
if age < 0 or age > 120:
raise ValueError("Idade fora do intervalo permitido")
return age
# Uso
print(process_age("25")) # 25
try:
process_age("abc")
except ValueError as e:
print(e) # Idade deve ser um número inteiroPara validação de strings, como e-mails ou URLs, é recomendável usar expressões regulares ou bibliotecas especializadas. O módulo re pode ser usado, mas cuidado com ReDoS (ataques de negação de serviço via regex). Prefira bibliotecas testadas como email-validator.
Segredos
Segredos incluem senhas, chaves de API, tokens de autenticação e outras informações sensíveis. Em Python, é comum que esses valores sejam armazenados em variáveis de ambiente ou arquivos de configuração. No entanto, muitos desenvolvedores cometem o erro de colocá-los diretamente no código-fonte, o que é extremamente perigoso.
A boa prática é nunca hardcodar segredos. Utilize variáveis de ambiente, arquivos como .env (que devem ser ignorados pelo git) ou serviços de gerenciamento de segredos (como AWS Secrets Manager ou HashiCorp Vault). A biblioteca python-dotenv facilita o carregamento de variáveis de ambiente a partir de um arquivo .env.
# Instale: pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv("API_KEY")
if not api_key:
raise RuntimeError("API_KEY não definida")
# Use api_key com segurançaAlém disso, ao lidar com senhas, nunca as armazene em texto puro. Use funções de hash seguras, como as fornecidas pela biblioteca bcrypt ou argon2. O módulo hashlib pode ser usado, mas lembre-se de usar um salt e um algoritmo adequado, como PBKDF2.
import hashlib
import os
# Gerar salt aleatório
salt = os.urandom(16)
# Hash da senha com PBKDF2
password = b"senha_super_secreta"
dk = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
print(dk.hex())Dependências (pip-audit)
Dependências de terceiros podem introduzir vulnerabilidades em seu projeto. O Python Package Index (PyPI) é um alvo comum para ataques de typosquatting (pacotes maliciosos com nomes semelhantes a pacotes populares). Portanto, é crucial auditar regularmente suas dependências.
Uma ferramenta essencial é o pip-audit, que verifica as dependências instaladas contra uma base de dados de vulnerabilidades conhecidas (CVE). Ele pode ser usado na linha de comando ou integrado ao CI/CD. A instalação é simples: pip install pip-audit.
# Instalar pip-audit
pip install pip-audit
# Auditar dependências do projeto
pip-audit
# Auditar um arquivo de requisitos
pip-audit -r requirements.txt
# Auditar e sair com código de erro se houver vulnerabilidades
pip-audit --fail-on=highAlém de auditar, é importante manter as dependências atualizadas. Use ferramentas como pip-tools para gerenciar versões de forma reproduzível, e considere o uso de ambientes virtuais para isolar projetos. Também é recomendável assinar e verificar a integridade dos pacotes (via GPG) quando possível.
eval e pickle (riscos)
As funções eval() e pickle.loads() são extremamente perigosas quando usadas com dados não confiáveis. eval() executa qualquer expressão Python, permitindo que um atacante execute código arbitrário. pickle é usado para serializar objetos Python, mas o processo de desserialização pode executar código malicioso.
O risco de eval é que ele interpreta strings como código. Se você passar entrada do usuário diretamente para eval, o usuário pode executar comandos como os.system('rm -rf /'). Nunca use eval com dados não confiáveis. Se precisar avaliar expressões matemáticas simples, use ast.literal_eval que é seguro para literais.
import ast
# Seguro: avalia apenas literais
value = ast.literal_eval("{'a': 1}")
print(value) # {'a': 1}
# Perigoso: eval executa código arbitrário
# eval("__import__('os').system('ls')") # Não faça isso!Da mesma forma, pickle deve ser usado apenas para dados confiáveis. Se você precisar desserializar dados de fontes não confiáveis, prefira formatos seguros como JSON. Para casos onde você precisa de um formato binário, considere msgpack ou protobuf.
Se você realmente precisa usar pickle, certifique-se de que os dados vêm de uma fonte confiável e que o ambiente está protegido. Nunca desserialize pickles de arquivos enviados por usuários.
Boas práticas gerais
Além dos tópicos específicos, algumas práticas gerais podem aumentar a segurança do seu código Python: sempre atualize a linguagem e as bibliotecas, use ambientes virtuais, minimize privilégios (não rode como root), e faça revisões de código regulares. Ferramentas como bandit podem analisar o código em busca de padrões inseguros.
# Instalar bandit
pip install bandit
# Analisar o código
bandit -r meu_projeto/Lembre-se: segurança é um processo contínuo. Mantenha-se informado sobre novas vulnerabilidades e atualize suas práticas conforme necessário.
Referências
- Documentação oficial da função eval
- Documentação oficial do pickle
- python-dotenv no PyPI
- pip-audit no PyPI
- Documentação do Bandit
- Documentação do hashlib
- OWASP Top Ten
Exercícios
- Validação de entrada: Escreva uma função
valida_cpfque recebe uma string e verifica se é um CPF válido (formato XXX.XXX.XXX-XX) usando expressões regulares e validação de dígitos verificadores. Considere apenas o formato, não a validação completa dos dígitos.✓ Resposta:import re def valida_cpf(cpf): if not re.match(r'^\d{3}\.\d{3}\.\d{3}-\d{2}$', cpf): return False return True print(valida_cpf("123.456.789-00")) # True print(valida_cpf("12345678900")) # False - Segredos: Crie um script que carrega uma chave de API de uma variável de ambiente chamada
MINHA_CHAVEe a imprime mascarada (apenas os primeiros 4 e últimos 4 caracteres visíveis).✓ Resposta:import os import dotenv dotenv.load_dotenv() chave = os.getenv("MINHA_CHAVE") if not chave: raise ValueError("MINHA_CHAVE não definida") if len(chave) <= 8: print("*" * len(chave)) else: print(f"{chave[:4]}{'*' * (len(chave)-8)}{chave[-4:]}") - Dependências: Explique como você integraria o
pip-auditem um pipeline de CI/CD (por exemplo, GitHub Actions). Liste os passos e o código YAML necessário.✓ Resposta:No GitHub Actions, você pode adicionar um passo que instala o pip-audit e executa a auditoria. Exemplo de workflow:
name: Security Audit on: [push, pull_request] jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies run: | pip install pip-audit pip install -r requirements.txt - name: Run pip-audit run: pip-audit --fail-on=high - eval e pickle: Dado o seguinte código inseguro, reescreva-o para usar
ast.literal_evalem vez deevale explique por que isso é mais seguro.dados = input("Digite um dicionário: ") dict_convertido = eval(dados) print(dict_convertido)✓ Resposta:import ast dados = input("Digite um dicionário: ") try: dict_convertido = ast.literal_eval(dados) print(dict_convertido) except (ValueError, SyntaxError): print("Entrada inválida")ast.literal_evalsó avalia literais (strings, números, tuplas, listas, dicionários, booleanos e None), portanto não executa código arbitrário. Isso impede que um usuário mal-intencionado execute comandos no sistema. - Boas práticas: Liste três medidas que você pode tomar para proteger segredos em um projeto Python e explique cada uma.✓ Resposta:
- Usar variáveis de ambiente: Armazenar segredos em variáveis de ambiente, fora do código-fonte, para que não sejam commitados acidentalmente.
- Utilizar um sistema de gerenciamento de segredos: Ferramentas como Vault ou AWS Secrets Manager permitem armazenar e acessar segredos de forma centralizada e com controle de acesso.
- Adicionar .env ao .gitignore: Se você usar um arquivo .env, certifique-se de que ele não seja versionado, adicionando-o ao .gitignore.