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.