Conceitos Fundamentais e OWASP Top 10
Tríade CIA
Confidencialidade
Dados acessíveis só por quem tem permissão. Ex: criptografia, controle de acesso, JWT.
Integridade
Dados não foram alterados sem autorização. Ex: hashes, assinaturas digitais, checksums.
Disponibilidade
Sistema acessível quando necessário. Ex: redundância, backups, proteção contra DDoS.
Autenticidade
Garantir que o usuário é quem diz ser. Ex: login, MFA, certificados digitais.
OWASP Top 10 — Vulnerabilidades mais críticas em APIs
| # | Vulnerabilidade | Exemplo | Defesa |
| 1 | Broken Object Level Auth | GET /pedidos/42 — ver pedido de outro usuário | Verificar se o recurso pertence ao usuário logado |
| 2 | Broken Authentication | Tokens sem expiração, senhas fracas | JWT com exp, bcrypt, MFA |
| 3 | Broken Object Property Auth | PATCH /usuario — alterar campo "admin: true" | Whitelist de campos editáveis |
| 4 | Unrestricted Resource Consumption | Sem limite de requisições — DDoS fácil | Rate limiting por IP/usuário |
| 5 | Broken Function Level Auth | Usuário comum acessa rota /admin/ | Middleware de verificação de roles |
| 6 | Unrestricted Access to Sensitive Flows | Sem limite em /esqueci-senha — força bruta | Rate limit + captcha em fluxos sensíveis |
| 7 | SSRF | API faz fetch de URL passada pelo usuário | Validar e bloquear URLs internas |
| 8 | Security Misconfiguration | Debug mode em produção, CORS aberto | Configuração revisada por ambiente |
| 9 | Improper Inventory Management | API v1 desatualizada ainda acessível | Deprecar e remover versões antigas |
| 10 | Unsafe Consumption of APIs | Confiar cegamente em API de terceiros | Validar e sanitizar dados externos |
⚠ BOLA — o mais crítico
Broken Object Level Authorization é o ataque mais comum em APIs. Ex: usuário 1 acessa
GET /pedidos/99 sendo que o pedido 99 é do usuário 2. Sempre verifique:
pedido.usuario_id == usuario_logado.id
Exercício 1: O que é BOLA e como prevenir?
→ Usuário acessa recurso de outro usuário apenas mudando o ID na URL. Previne verificando propriedade do recurso.
if pedido.usuario_id != current_user.id: raise HTTPException(403). Simples assim — mas esquecido com frequência em APIs mal projetadas.
Ataques Comuns e Como Defender
SQL Injection
# VULNERÁVEL — concatenação de string na query
@app.get("/usuarios")
def buscar(nome: str):
query = f"SELECT * FROM usuarios WHERE nome = '{nome}'"
# Se nome = "'; DROP TABLE usuarios; --"
# vira: SELECT * FROM usuarios WHERE nome = ''; DROP TABLE usuarios; --'
return db.execute(query)
# SEGURO — query parametrizada (ORM ou parâmetros)
def buscar_seguro(nome: str):
# SQLAlchemy — parametrizado automaticamente
return db.query(Usuario).filter(Usuario.nome == nome).all()
# SQL puro com parâmetros
return db.execute("SELECT * FROM usuarios WHERE nome = :nome", {"nome": nome})
XSS — Cross-Site Scripting
# XSS acontece quando o backend retorna dados do usuário sem sanitizar
# e o frontend renderiza como HTML
# Vulnerável: retorna HTML com input do usuário
comentario = request.body.comentario
return f"
{comentario}
"
# Se comentario = ""
# o script executa no browser de quem ver!
# Defesa: escape de HTML + Content-Security-Policy
import html
comentario_seguro = html.escape(comentario)
# Header CSP no Nginx:
add_header Content-Security-Policy "default-src 'self';"
CSRF — Cross-Site Request Forgery
# CSRF: site malicioso faz requisição autenticada em nome do usuário
# Ex: usuário logado no banco, acessa site malicioso que faz:
# POST bank.com/transferir com os cookies do usuário!
# Defesa 1: CSRF Token (formulários)
# Defesa 2: SameSite cookies
response.set_cookie("session", value, samesite="strict")
# Defesa 3: APIs REST com JWT no header (não cookie)
# O header Authorization não é enviado automaticamente pelo browser
# — CSRF não funciona com JWT no header!
Rate Limiting — Proteção contra Força Bruta e DDoS
pip install slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
# Limitar rotas específicas
@app.post("/auth/login")
@limiter.limit("5/minute") # max 5 tentativas por minuto por IP
async def login(request: Request, dados: LoginSchema):
...
@app.post("/auth/esqueci-senha")
@limiter.limit("3/hour") # max 3 por hora
async def esqueci_senha(request: Request, email: str):
...
Principais Vetores de Ataque
| Ataque | Como funciona | Defesa principal |
| SQL Injection | Injeta SQL em inputs para manipular o banco | ORM / queries parametrizadas |
| XSS | Injeta JavaScript malicioso via inputs | Escape de HTML, CSP header |
| CSRF | Site malicioso faz requests autenticados | JWT no header, SameSite cookies |
| Força Bruta | Testa senhas até acertar | Rate limit, bloqueio de IP, MFA |
| DDoS | Inunda o servidor de requisições | CDN, rate limit, WAF, Cloudflare |
| Man-in-the-Middle | Intercepta comunicação | HTTPS obrigatório, HSTS |
| Credential Stuffing | Usa senhas vazadas de outros sites | MFA, pwned passwords API |
Exercício 2: Por que APIs REST com JWT no header são mais seguras contra CSRF?
→ O browser não envia o header Authorization automaticamente em requests cross-origin — diferente de cookies.
CSRF explora o fato de browsers enviarem cookies automaticamente para o domínio. JWT no header Authorization requer JavaScript explícito para enviar — um site malicioso não consegue fazer isso por restrições do CORS.
Criptografia
Tipos de Criptografia
Simétrica
Mesma chave para criptografar e descriptografar.
Rápida, ideal para grandes volumes.
Ex: AES-256.
Problema: como compartilhar a chave com segurança?
Assimétrica (Par de Chaves)
Chave pública (criptografa) + chave privada (descriptografa).
Mais lenta, usada para trocar chaves e assinar.
Ex: RSA, ECDSA.
Base do HTTPS e assinaturas digitais.
Hash (One-way)
Entrada → saída de tamanho fixo. Irreversível.
Ex: bcrypt (senhas), SHA-256 (integridade), MD5 (obsoleto para segurança).
Mesmo input = mesmo output sempre.
TLS/SSL
Combina assimétrica (para trocar chave de sessão) + simétrica (para dados).
HTTPS = HTTP + TLS. Garante confidencialidade e integridade da comunicação.
Hashing de Senhas — bcrypt
from passlib.context import CryptContext
pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Criar hash
senha_hash = pwd.hash("minha_senha_123")
# $2b$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
# Verificar
pwd.verify("minha_senha_123", senha_hash) # True
pwd.verify("senha_errada", senha_hash) # False
# Por que bcrypt e não SHA-256 para senhas?
# SHA-256: rápido demais — atacante testa bilhões por segundo
# bcrypt: lento por design (fator de custo configurável)
# bcrypt com cost=12 → ~100ms por hash → força bruta inviável
JWT — Assinatura e Verificação
from jose import jwt
from datetime import datetime, timedelta
SECRET = "chave-secreta-minima-256-bits-de-comprimento"
ALGO = "HS256" # HMAC com SHA-256
# Criar token
def criar_token(user_id: int, role: str) -> str:
payload = {
"sub": str(user_id), # subject (ID do usuário)
"role": role,
"iat": datetime.utcnow(), # issued at
"exp": datetime.utcnow() + timedelta(minutes=30) # expira em 30min
}
return jwt.encode(payload, SECRET, algorithm=ALGO)
# Verificar token
def verificar_token(token: str) -> dict:
return jwt.decode(token, SECRET, algorithms=[ALGO])
# Lança JWTError se inválido ou expirado
# HS256 = assinatura simétrica (mesma chave para assinar e verificar)
# RS256 = assinatura assimétrica (privada assina, pública verifica) — melhor para microsserviços
⚠ Nunca armazene dados sensíveis no JWT
O payload do JWT é apenas
codificado em Base64, não criptografado. Qualquer pessoa pode decodificar e ler o conteúdo. Guarde apenas id e role — nunca senha, CPF ou dados pessoais sensíveis.
Exercício 3: Por que bcrypt é melhor que SHA-256 para senhas?
→ bcrypt é lento por design. SHA-256 é tão rápido que atacantes testam bilhões de combinações por segundo.
SHA-256 em GPU: ~10 bilhões hashes/s. bcrypt cost=12: ~100ms por hash = ~10 hashes/s. A senha "senha123" em SHA-256: quebrada em segundos. Em bcrypt: anos. O custo é configurável — aumenta com hardware mais rápido.
Segurança em APIs Python — Checklist Prático
Configurações de Segurança no FastAPI
from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
import secrets
app = FastAPI()
# CORS — restrinja em produção
app.add_middleware(CORSMiddleware,
allow_origins=["https://meuapp.com"], # NÃO use ["*"] em produção
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
allow_credentials=True,
)
# Trusted Host — evita Host header attacks
app.add_middleware(TrustedHostMiddleware,
allowed_hosts=["meuapp.com", "api.meuapp.com"]
)
# Security headers via Nginx (não no FastAPI)
# add_header X-Frame-Options "DENY";
# add_header X-Content-Type-Options "nosniff";
# add_header Strict-Transport-Security "max-age=31536000";
# add_header X-XSS-Protection "1; mode=block";
Autenticação e Autorização
from enum import Enum
from functools import wraps
class Role(str, Enum):
ADMIN = "admin"
USER = "user"
security = HTTPBearer()
# Dependência de autenticação
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Security(security)
):
token = credentials.credentials
try:
payload = verificar_token(token)
return payload
except:
raise HTTPException(401, "Token inválido ou expirado")
# Dependência de autorização por role
def requer_role(role: Role):
async def verificar(user=Depends(get_current_user)):
if user.get("role") != role:
raise HTTPException(403, "Sem permissão")
return user
return verificar
# Uso nas rotas
@app.get("/admin/usuarios")
async def admin_listar(user=Depends(requer_role(Role.ADMIN))):
return {"msg": "só admin chega aqui"}
@app.get("/perfil")
async def meu_perfil(user=Depends(get_current_user)):
return {"id": user["sub"], "role": user["role"]}
# BOLA — verificar propriedade do recurso
@app.get("/pedidos/{pedido_id}")
async def get_pedido(pedido_id: int, user=Depends(get_current_user)):
pedido = db.query(Pedido).get(pedido_id)
if not pedido:
raise HTTPException(404)
if pedido.usuario_id != int(user["sub"]): # BOLA check!
raise HTTPException(403, "Não é seu pedido")
return pedido
Checklist de Segurança para Deploy
| Item | Como verificar |
| HTTPS ativo | curl -I https://minha-api.com → status 200 |
| Senhas com bcrypt | Revisar código — nunca md5/sha1/texto puro |
| JWT com expiração | Token tem campo "exp" no payload |
| CORS restrito | allow_origins sem "*" em produção |
| Rate limiting ativo | Tentar 100 logins/min — deve bloquear |
| Variáveis de ambiente | .env no .gitignore, secrets no servidor |
| ORM em todas queries | Nenhum f-string com input do usuário em SQL |
| Debug mode desligado | DEBUG=False em produção |
| Logs sem dados sensíveis | Revisar logs — sem senha, CPF, token |
| BOLA verificado | Testar acessar recurso de outro usuário → 403 |
📝 Cola — Segurança da Informação
Tríade CIA
| Pilar | Garantia | Como |
| Confidencialidade | Só quem tem permissão acessa | Criptografia, JWT, RBAC |
| Integridade | Dados não foram alterados | Hash, assinatura digital |
| Disponibilidade | Sistema acessível | Redundância, anti-DDoS |
OWASP — Os 3 mais importantes
| Vulnerabilidade | Defesa |
| BOLA — acesso a recurso alheio | Verificar pedido.user_id == current_user.id |
| SQL Injection | ORM / queries parametrizadas — nunca f-string |
| Força Bruta | Rate limit em /login e /esqueci-senha |
⭐ Regras de Ouro
Regra
Senhas: sempre
bcrypt — nunca MD5, SHA1 ou texto puro
Regra
JWT: só id e role no payload —
nunca dados sensíveis (é Base64, não criptografado)
Regra
SQL: nunca concatenar input do usuário — use
ORM ou parâmetros
Regra
BOLA: sempre verifique se o
recurso pertence ao usuário logado
Regra
CORS:
nunca "*" em produção — liste os domínios permitidos
Regra
401 = não autenticado. 403 = autenticado mas
sem permissão