OWASP Top 10 2021: o que mudou e como se proteger
O OWASP Top 10 é a referência mais importante em segurança de aplicações web. Em 2021, a lista foi atualizada com mudanças significativas. Vamos entender cada item e como se proteger com exemplos em Python.
O que mudou na 2021
| 2017 | 2021 | Mudança |
|---|---|---|
| Injection | A03: Injection | Subiu 2 posições |
| Broken Authentication | A07: Identity and Authentication Failures | Renomeado |
| XSS | A03: Injection (incluído) | Fundido em Injection |
| Broken Access Control | A01: Broken Access Control | Agora é #1 |
| Security Misconfiguration | A05: Security Misconfiguration | Mantido |
| Novo | A04: Insecure Design | Nova categoria |
| Novo | A08: Software and Data Integrity Failures | Nova categoria |
| Novo | A10: Server-Side Request Forgery (SSRF) | Nova categoria |
A01: Broken Access Control
A vulnerabilidade #1 de 2021. Acontece quando usuários podem acessar recursos que não deveriam.
# ERRADO: qualquer usuário pode ver qualquer perfil
@app.route("/usuarios/<int:id>")
def ver_perfil(id):
usuario = db.get_usuario(id)
return jsonify(usuario)
# CORRETO: verificar se o usuário tem permissão
@app.route("/usuarios/<int:id>")
@autenticado
def ver_perfil(id):
if request.usuario_id != id and not request.usuario_admin:
return jsonify({"erro": "Acesso negado"}), 403
usuario = db.get_usuario(id)
return jsonify(usuario)
Prevenção:
- Deny by default — negue tudo, permita explicitamente
- Verifique permissões em cada endpoint
- Use IDs do token JWT, não de parâmetros da URL
- Logue tentativas de acesso negado
A02: Cryptographic Failures
Dados sensíveis sem criptografia adequada. Inclui senhas em MD5, dados em trânsito sem HTTPS, chaves hardcoded.
# ERRADO
import hashlib
senha_hash = hashlib.md5(senha.encode()).hexdigest()
# CORRETO
import bcrypt
senha_hash = bcrypt.hashpw(senha.encode(), bcrypt.gensalt(rounds=12))
Prevenção:
- Use bcrypt, argon2 ou scrypt para senhas
- HTTPS em todas as rotas
- Nunca hardcode chaves — use variáveis de ambiente
- Criptografe dados sensíveis em repouso
A03: Injection
SQL injection, NoSQL injection, command injection, LDAP injection…
# ERRADO: SQL injection
query = f"SELECT * FROM usuarios WHERE email = '{email}'"
# CORRETO: query parametrizada
cursor.execute("SELECT * FROM usuarios WHERE email = %s", (email,))
# ERRADO: command injection
import os
os.system(f"ping {host}")
# CORRETO: usar subprocess com lista
import subprocess
subprocess.run(["ping", "-c", "3", host], capture_output=True)
A04: Insecure Design (NOVO)
Falhas que existem porque o sistema foi projetado sem pensar em segurança desde o início.
Exemplo: um sistema de recuperação de senha que envia a nova senha por e-mail sem expiração:
# ERRADO
def recuperar_senha(email):
nova_senha = gerar_senha_aleatoria()
db.atualizar_senha(email, nova_senha) # Válida para sempre
enviar_email(email, f"Sua nova senha: {nova_senha}")
# CORRETO
def recuperar_senha(email):
token = secrets.token_urlsafe(32)
expiracao = datetime.utcnow() + timedelta(hours=1)
db.salvar_token_recuperacao(email, token, expiracao)
enviar_email(email, f"Use este link: /reset?token={token}")
Prevenção:
- Threat modeling desde o design
- Use princípios de segurança por design
- Limite tentativas de login
- Tokens com expiração
A05: Security Misconfiguration
Headers faltando, credenciais padrão, debug em produção.
# Checklist de configuração
@app.after_request
def headers_seguranca(response):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Strict-Transport-Security"] = "max-age=31536000"
response.headers["Content-Security-Policy"] = "default-src 'self'"
return response
# Desabilite debug em produção
app.run(debug=False)
A06: Vulnerable and Outdated Components
Usar bibliotecas com CVEs conhecidas.
# Python: verifique vulnerabilidades
pip audit
# Node
npm audit
# Go
govulncheck ./...
Prevenção:
- Automatize updates com Dependabot ou Renovate
- Monitore CVEs das suas dependências
- Mantenha um SBOM (Software Bill of Materials)
A07: Identity and Authentication Failures
Autenticação fraca: senhas sem requisitos, sem MFA, sem rate limiting.
# Checklist de autenticação
def validar_senha(senha: str) -> list[str]:
erros = []
if len(senha) < 12:
erros.append("Mínimo 12 caracteres")
if not any(c.isupper() for c in senha):
erros.append("Precisa de maiúscula")
if not any(c.isdigit() for c in senha):
erros.append("Precisa de número")
if senha in SENHAS_COMUNS:
erros.append("Senha muito comum")
return erros
A08: Software and Data Integrity Failures (NOVO)
Inclui supply chain attacks (como SolarWinds) e desserialização insegura.
# ERRADO: desserializar dados não confiáveis com pickle
import pickle
dados = pickle.loads(dados_do_usuario) # Pode executar código arbitrário!
# CORRETO: usar JSON para dados não confiáveis
import json
dados = json.loads(dados_do_usuario)
A09: Security Logging and Monitoring Failures
Sem logs, sem detecção, sem resposta.
import logging
logger = logging.getLogger("seguranca")
# Log de eventos críticos
def login(email, senha):
usuario = autenticar(email, senha)
if usuario:
logger.info(f"Login sucesso: {email} de {request.remote_addr}")
else:
logger.warning(f"Login falhou: {email} de {request.remote_addr}")
# Alertar em muitas falhas
falhas_recentes = contar_falhas(email, minutos=15)
if falhas_recentes > 5:
logger.critical(f"Possível brute force: {email}")
A10: Server-Side Request Forgery (SSRF) (NOVO)
O servidor faz requests para URLs controladas pelo atacante.
# ERRADO: servidor busca URL fornecida pelo usuário
@app.route("/preview")
def preview():
url = request.args.get("url")
resposta = requests.get(url) # SSRF!
return resposta.text
# CORRETO: validar e restringir URLs
import ipaddress
from urllib.parse import urlparse
def url_segura(url: str) -> bool:
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return False
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback:
return False
except ValueError:
pass # hostname é domínio, ok
return True
@app.route("/preview")
def preview():
url = request.args.get("url")
if not url_segura(url):
return jsonify({"erro": "URL não permitida"}), 400
resposta = requests.get(url, timeout=5)
return resposta.text
Conclusão
O OWASP Top 10 2021 reflete a realidade atual: ataques à cadeia de suprimentos, falhas de design, e SSRF entraram pela primeira vez. Broken Access Control agora é a vulnerabilidade #1.
Use esta lista como checklist nos seus projetos. Implemente as prevenções desde o início — corrigir depois é sempre mais caro.
Caso eu tenha falado alguma besteira, por favor, agradecerei correções e sugestões.