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ão | Objetivo | Prioridade | OWASP |
|---|---|---|---|
| ① Geração aleatória segura | Tokens imprevisíveis | Obrigatório | A02 Falhas criptográficas |
| ② Armazenamento de senhas | Proteção de credenciais | Obrigatório | A02 Falhas criptográficas |
| ③ Design seguro de JWT | Controle de autenticação | Obrigatório | A07 Falhas de autenticação |
| ④ Prevenção de SQL Injection | Proteção do banco de dados | Obrigatório | A03 Injeção |
| ⑤ Prevenção de XSS | Segurança de exibição | Obrigatório | A03 Injeção |
| ⑥ Proteção CSRF | Segurança de formulários | Obrigatório | A01 Controle de acesso quebrado |
| ⑦ Limitação de taxa | Prevenção de abuso | Importante | — |
| ⑧ Design de logs | Prevenção de incidentes | Importante | A09 Falhas de registro |
| ⑨ Gestão de secrets | Prevenção de vazamentos | Obrigatório | A02 Falhas criptográficas |
| ⑩ Configuração por ambiente | Segurança operacional | Obrigatório | A05 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:
import random
token = random.randint(100000, 999999) # Previsível!
Exemplo correto:
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.
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:
password = "user_password"
db.save(password) # Texto plano → brecha instantânea
Exemplo correto:
import bcrypt
password = b"mypassword"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# Verificar
is_valid = bcrypt.checkpw(password, hashed)
print(is_valid) # True
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.
“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:
import jwt
token = jwt.encode({"user_id": 1}, "secret") # Sem expiração, secret fraco
Exemplo correto:
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"])
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
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:
# 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:
# 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.
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:
# Retornando entrada do usuário diretamente
return user_input
# Se o atacante inserir <script>alert(1)</script> ele executa
Exemplo correto:
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().
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):
<form method="POST">
{% csrf_token %}
<input type="text" name="data">
<button type="submit">Enviar</button>
</form>
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)
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.
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"}
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
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:
# Nunca faça isso
print(f"Login: user={username}, password={password}")
logging.info(f"Token: {api_token}")
Exemplo correto:
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)
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:
# Hardcoded → vazamento instantâneo ao fazer push
SECRET_KEY = "abc123superSecret"
DB_PASSWORD = "production_password"
Exemplo correto:
import os
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
pip install python-dotenv
Exemplo de arquivo .env:
SECRET_KEY=9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
DB_PASSWORD=strong_random_password_here
E sempre adicione .env ao seu .gitignore.
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.
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
secretspara 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ção | Incidente | Contramedida |
|---|---|---|
| #1 | Secrets no Git | .env + .gitignore |
| #2 | DEBUG ativo em produção | Configuração por ambiente |
| #3 | Concatenação de strings SQL | Parameter binding |
| #4 | JWT sem expiração | Configuração obrigatória de exp |
| #5 | Sem limitação de taxa | slowapi / 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:
- Gerar valores aleatórios seguros com
secrets - Hashear senhas com bcrypt
- Gerenciar secrets via variáveis de ambiente
- Usar parameter binding para segurança SQL
- 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.

Leave a Reply