10 Padrões de Segurança em Python — Guia Prático com Código para Ferramentas Web Seguras

Ao desenvolver ferramentas web e APIs com Python, a maioria dos desenvolvedores se concentra na implementação de funcionalidades. Mas em projetos reais, a qualidade do design de segurança equivale à qualidade do serviço. Um serviço sem proteção de dados, resistência a ataques e segurança operacional nunca ganhará confiança.

Este artigo cobre 10 padrões de segurança que todo desenvolvedor Python deveria implementar no mínimo, com exemplos de código NG (ruim) e OK (bom). Seja usando Django, FastAPI ou escrevendo scripts Python, este guia é para você.

Para conhecimentos fundamentais sobre senhas, hashes e tokens, consulte “Entendendo Senhas, UUIDs, Hashes e Tokens.”

Visão Geral: 10 Padrões de Segurança em Python

Comecemos com a visão geral. Aqui estão os 10 padrões de relance, com links para cada seção.

PadrãoObjetivoPrioridadeOWASP
① Geração aleatória seguraTokens imprevisíveisObrigatórioA02 Falhas criptográficas
② Armazenamento de senhasProteção de credenciaisObrigatórioA02 Falhas criptográficas
③ Design seguro de JWTControle de autenticaçãoObrigatórioA07 Falhas de autenticação
④ Prevenção de SQL InjectionProteção do banco de dadosObrigatórioA03 Injeção
⑤ Prevenção de XSSSegurança de exibiçãoObrigatórioA03 Injeção
⑥ Proteção CSRFSegurança de formuláriosObrigatórioA01 Controle de acesso quebrado
⑦ Limitação de taxaPrevenção de abusoImportante
⑧ Design de logsPrevenção de incidentesImportanteA09 Falhas de registro
⑨ Gestão de secretsPrevenção de vazamentosObrigatórioA02 Falhas criptográficas
⑩ Configuração por ambienteSegurança operacionalObrigatórioA05 Má configuração

Incluímos também o mapeamento OWASP Top 10. Cobrir estes 10 padrões por si só aborda a maioria dos riscos identificados pelo OWASP.

① Geração Aleatória Segura — Nunca Use random

Ao gerar tokens, códigos de autenticação ou URLs de redefinição de senha, o módulo random do Python nunca deve ser usado. random usa o algoritmo Mersenne Twister, que é previsível — um atacante que observe algumas centenas de saídas pode prever valores futuros.

Exemplo ruim:

NG
import random
token = random.randint(100000, 999999)  # Previsível!

Exemplo correto:

OK
import secrets

token = secrets.token_hex(16)
print(token)
# Exemplo: 9f3c0c6d61c3c2e7b2c5a2b41a6f9d88

O módulo secrets utiliza o gerador de números pseudoaleatórios criptograficamente seguro (CSPRNG) do sistema, tornando a previsão praticamente impossível. Use secrets para toda aleatoriedade relacionada à segurança.

💡 Tip

secrets.token_urlsafe(32) gera tokens seguros para URLs, perfeitos para links de redefinição de senha. Consulte “Padrões de Geração de Senhas em Python” para mais detalhes.

② Armazenamento de Senhas — Texto Plano É Inaceitável

Armazenar senhas em texto plano é uma das falhas de segurança mais catastróficas. No momento em que seu banco de dados vaza, todas as senhas dos usuários ficam nas mãos do atacante.

Exemplo ruim:

NG
password = "user_password"
db.save(password)  # Texto plano → brecha instantânea

Exemplo correto:

OK
import bcrypt

password = b"mypassword"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())

# Verificar
is_valid = bcrypt.checkpw(password, hashed)
print(is_valid)  # True
Bash
pip install bcrypt

Usar funções hash genéricas como SHA-256 sozinhas também é perigoso. SHA-256 é rápido demais — atacantes podem testar bilhões de hashes por segundo. bcrypt e Argon2 são intencionalmente lentos, tornando ataques de força bruta exponencialmente mais caros.

⚠️ Armadilha comum

“Fiz hash com SHA-256, então é seguro” está errado. Sempre use algoritmos intencionalmente lentos como bcrypt, Argon2 ou scrypt para armazenamento de senhas. Para fundamentos de hashing, consulte “Senhas, UUIDs, Hashes e Tokens.”

③ Design Seguro de JWT

JWT (JSON Web Token) é amplamente usado para autenticação stateless, mas uma configuração incorreta gera vulnerabilidades graves.

Exemplo ruim:

NG
import jwt
token = jwt.encode({"user_id": 1}, "secret")  # Sem expiração, secret fraco

Exemplo correto:

OK
import jwt
import datetime

payload = {
    "user_id": 1,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, "STRONG_SECRET_KEY_HERE", algorithm="HS256")

# Verificar
data = jwt.decode(token, "STRONG_SECRET_KEY_HERE", algorithms=["HS256"])
Bash
pip install PyJWT

Três regras para um design JWT seguro:

  • Sempre definir expiração (exp) — tokens sem expiração podem ser explorados para sempre se vazarem
  • Especificar o algoritmo explicitamente — omiti-lo arrisca ataques de algoritmo “none”
  • Usar uma chave secreta forte — gerar com secrets.token_hex(32) e armazenar em variáveis de ambiente
💡 Tip

A expiração do access token deve ser de 15 minutos a 1 hora. Para sessões persistentes, combine com refresh tokens e mantenha os access tokens de vida curta.

④ Prevenção de SQL Injection

A injeção SQL é uma das vulnerabilidades mais antigas e ainda mais comuns. Concatenar entrada do usuário diretamente em instruções SQL permite que atacantes manipulem seu banco de dados à vontade.

Exemplo ruim:

NG
# Nunca faça isso
query = "SELECT * FROM users WHERE name='" + name + "'"
cursor.execute(query)

Se name contiver admin' OR '1'='1, todos os dados de usuários ficam expostos.

Exemplo correto:

OK
# Usar parameter binding
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))

Com parameter binding, a entrada do usuário é tratada como dados, não como parte da instrução SQL. Django ORM e SQLAlchemy usam essa abordagem por padrão, mas sempre use parameter binding ao escrever SQL cru.

⚠️ Armadilha comum

f-strings (f"SELECT ... WHERE name='{name}'") são tão perigosas quanto concatenação. Podem parecer mais limpas, mas o risco de injeção SQL é idêntico.

⑤ Prevenção de XSS

XSS (Cross-Site Scripting) ocorre quando a entrada do usuário é renderizada diretamente em HTML, permitindo a execução de scripts maliciosos no navegador.

Exemplo ruim:

NG
# Retornando entrada do usuário diretamente
return user_input
# Se o atacante inserir <script>alert(1)</script> ele executa

Exemplo correto:

OK
import html

safe_output = html.escape(user_input)
return safe_output
#  <script> → <script> — neutralizado

O princípio de prevenção XSS é “nunca confie na saída”. Escape na etapa de saída (ao renderizar HTML), não na entrada. Os templates Django e Jinja2 habilitam escape HTML por padrão, mas tenha cuidado especial com filtros |safe ou mark_safe().

💡 Tip

Ao usar {{ variable|safe }} em templates Django, aplique apenas a dados confiáveis. Aplicar |safe à entrada do usuário é abrir a porta para XSS.

⑥ Proteção CSRF

CSRF (Cross-Site Request Forgery) engana usuários autenticados para enviar requisições não desejadas. A defesa é incorporar tokens emitidos pelo servidor em formulários e validá-los no envio.

Django (integrado):

Django Template
<form method="POST">
    {% csrf_token %}
    <input type="text" name="data">
    <button type="submit">Enviar</button>
</form>

FastAPI:

FastAPI
from itsdangerous import URLSafeSerializer

s = URLSafeSerializer("secret-key")
csrf_token = s.dumps("session_id")

# Verificar
try:
    data = s.loads(received_token)
except Exception:
    raise HTTPException(status_code=403)
Bash
pip install itsdangerous

Django tem proteção CSRF habilitada por padrão. Para frameworks sem proteção CSRF integrada como FastAPI, você precisará implementar a geração e validação de tokens.

⑦ Limitação de Taxa (Rate Limiting)

A limitação de taxa restringe o número de requisições em uma janela de tempo. Previne ataques de força bruta no login, abuso de API e flooding de bots.

FastAPI + slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.get("/api/data")
@limiter.limit("10/minute")
def get_data():
    return {"status": "ok"}
Bash
pip install slowapi

Para Django, django-ratelimit é a escolha padrão. Sempre defina limites de taxa nestes endpoints:

  • Login — alvo direto de ataques de força bruta
  • Redefinição de senha — previne bombardeio de emails
  • Endpoints de API — previne abuso e escalada de custos
  • Formulários de registro — previne criação de contas spam
💡 Tip

A limitação de taxa não “previne completamente” ataques — aumenta dramaticamente o custo de atacar. Combiná-la com limitação de taxa no Nginx bloqueia requisições antes de chegarem à sua aplicação.

⑧ Design de Logs — O Que Você Nunca Deve Registrar

Logging é essencial para debugging e monitoramento de segurança, mas há informações que nunca devem aparecer nos logs. Se senhas, tokens, API keys ou session IDs acabarem em arquivos de log, esses arquivos se tornam vetores de ataque.

Exemplo ruim:

NG
# Nunca faça isso
print(f"Login: user={username}, password={password}")
logging.info(f"Token: {api_token}")

Exemplo correto:

OK
import logging

logging.info("Login attempt: user=%s, ip=%s", username, request_ip)
logging.warning("Failed login: user=%s, ip=%s", username, request_ip)

As regras são simples:

  • Nunca registrar: senhas, tokens, API keys, cookies, dados pessoais
  • Registrar: IDs de usuário, endereços IP, nomes de ação, timestamps, resultados (sucesso/falha)
⚠️ Armadilha comum

Fazer deploy com DEBUG=True em produção expõe stack traces, variáveis de ambiente e detalhes de conexão do banco de dados. No Django, sempre configure DEBUG=False em produção.

⑨ Gestão de Secrets

API keys, senhas de banco de dados, chaves secretas JWT — esses secrets nunca devem ser hardcoded no código-fonte. No momento em que seu código aparece no GitHub, tudo vaza.

Exemplo ruim:

NG
# Hardcoded → vazamento instantâneo ao fazer push
SECRET_KEY = "abc123superSecret"
DB_PASSWORD = "production_password"

Exemplo correto:

OK
import os
from dotenv import load_dotenv

load_dotenv()

SECRET_KEY = os.getenv("SECRET_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
Bash
pip install python-dotenv

Exemplo de arquivo .env:

.env
SECRET_KEY=9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
DB_PASSWORD=strong_random_password_here

E sempre adicione .env ao seu .gitignore.

💡 Tip

O GitHub tem “Secret Scanning” que detecta automaticamente API keys comprometidas. No entanto, atacantes podem ser mais rápidos. A melhor defesa é nunca fazer commit de secrets.

⑩ Configuração por Ambiente

Usar a mesma configuração em desenvolvimento, staging e produção convida acidentes. Modo debug ativo em produção, conexões de teste manipulando dados em produção — estes são incidentes reais.

settings.py
import os

ENV = os.getenv("ENV", "development")

if ENV == "production":
    DEBUG = False
    ALLOWED_HOSTS = ["example.com"]
else:
    DEBUG = True
    ALLOWED_HOSTS = ["*"]

Separar a configuração por variáveis de ambiente permite mudar o comportamento por ambiente sem alterar código. Para Django, django-environ adiciona gestão tipada; para FastAPI, pydantic-settings oferece benefícios similares.

Checklist de Segurança e Incidentes Comuns

Verifique esta lista antes de cada lançamento:

  • □ Usando secrets para geração aleatória?
  • □ Senhas hasheadas com bcrypt/Argon2?
  • □ JWT tem expiração (exp)?
  • □ SQL usa parameter binding?
  • □ Saída HTML escapada?
  • □ Formulários incluem tokens CSRF?
  • □ Login/API tem limitação de taxa?
  • □ Logs sem senhas/tokens?
  • □ Secrets gerenciados via variáveis de ambiente?
  • □ DEBUG=False em produção?

Conheça também os incidentes de segurança mais comuns:

PosiçãoIncidenteContramedida
#1Secrets no Git.env + .gitignore
#2DEBUG ativo em produçãoConfiguração por ambiente
#3Concatenação de strings SQLParameter binding
#4JWT sem expiraçãoConfiguração obrigatória de exp
#5Sem limitação de taxaslowapi / django-ratelimit

Prevenir apenas esses cinco cobre a grande maioria dos incidentes de segurança encontrados na prática.

Perguntas Frequentes

P: Qual é o mínimo para segurança em Python?

secrets (aleatório seguro), bcrypt (armazenamento de senhas), variáveis de ambiente (gestão de secrets), parameter binding (segurança SQL) e html.escape (prevenção XSS). Esses cinco sozinhos criam uma diferença enorme em relação a aplicações sem segurança.

P: Django é seguro por padrão?

Os padrões do Django são sólidos — tokens CSRF, escape XSS e prevenção de injeção SQL estão todos integrados. Porém, má configuração é comum: deixar DEBUG=True em produção, mau uso de filtros |safe e SQL cru descuidado são fontes frequentes de vulnerabilidades.

P: JWT é sempre necessário?

Não. Para aplicações web pequenas, autenticação baseada em sessões funciona bem. JWT brilha em arquiteturas de microserviços ou SPAs onde autenticação stateless é necessária.

P: Limitação de taxa é necessária?

Para qualquer API ou formulário de login acessível publicamente, sim. Pode não ser necessária para scripts internos, mas endpoints públicos sem limitação de taxa são alvos fáceis para bots e ataques de força bruta.

P: Qual é o incidente de segurança mais comum?

Secrets (API keys, senhas) enviados ao GitHub. Bots escaneiam o GitHub constantemente buscando API keys, e a exploração pode ocorrer minutos após a exposição.

Conclusão

Construir ferramentas web seguras em Python não requer criptografia avançada — requer dominar os fundamentos. Entre os 10 padrões cobertos, aqui estão os 5 principais:

  1. Gerar valores aleatórios seguros com secrets
  2. Hashear senhas com bcrypt
  3. Gerenciar secrets via variáveis de ambiente
  4. Usar parameter binding para segurança SQL
  5. Prevenir XSS com html.escape

Segurança não é algo que se adiciona depois — é algo que se projeta desde o início. Este princípio é a base do desenvolvimento Python profissional.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *