CORS, CSRF, cookies seguros: segurança web fundamental
CORS, CSRF e configuração de cookies são as defesas de segurança que mais desenvolvedores configuram errado — seja abrindo demais (CORS: *) ou esquecendo atributos críticos em cookies de sessão. Entender o modelo de ameaça por trás de cada um evita configurações de boa vontade mas inseguras.
Same-Origin Policy e CORS
# Origin = protocolo + host + porta
# https://app.example.com:443 ← origem completa
# http://app.example.com:80 ← origem DIFERENTE (http vs https)
# https://api.example.com:443 ← origem DIFERENTE (host diferente)
# https://app.example.com:8443 ← origem DIFERENTE (porta diferente)
# Preflight (OPTIONS) é enviado para requests "não simples":
# Não-simples: método != GET/POST/HEAD, ou headers customizados, ou Content-Type != form/text
# Exemplo de preflight:
# OPTIONS /api/users HTTP/1.1
# Origin: https://app.example.com
# Access-Control-Request-Method: POST
# Access-Control-Request-Headers: Content-Type, Authorization
# Resposta do servidor:
# HTTP/1.1 200 OK
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Max-Age: 86400 ← cacheia o preflight por 24h
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS middleware — configuração CORRETA para produção:
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.example.com",
"https://admin.example.com",
], # NUNCA "*" se usar credentials
allow_credentials=True, # permite cookies cross-origin
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Request-ID"],
max_age=86400, # cache do preflight
)
# Armadilha: allow_origins=["*"] + allow_credentials=True = INVÁLIDO
# Browser recusa se origin for * e credentials estiver true
# Deve ser origem específica quando credentials=True
# CORS NÃO protege de CSRF!
# CORS bloqueia LEITURA da resposta, não o ENVIO da request
# Um form HTML pode fazer POST cross-origin sem CORS (é "simples")
# → CSRF protection é separada de CORS- → se não for simples
- → libera a LEITURA
- → bloqueia
- Fora da AWS
- Segurança e identidade
- Rede e entrega
- Conceito de arquitetura
A confusão que este desenho existe para desfazer: política de origem e requisição forjada parecem o mesmo problema e não são. Uma restringe a LEITURA da resposta por código de outra origem; a outra é sobre o ENVIO de requisição com a credencial do usuário — e só o atributo do cookie resolve a segunda.
- 1 · A restrição é do navegador, não do servidor. Nada impede um programa qualquer de chamar a mesma API. A política existe para proteger o USUÁRIO de código que roda no navegador dele.
- 2 · A verificação prévia é uma pergunta antes da ação. Método diferente do trivial ou cabeçalho personalizado disparam uma consulta ao servidor antes da requisição real. É o que evita que a requisição perigosa chegue a acontecer.
- 3 · Quem autoriza é o servidor de destino. O cabeçalho de permissão vem na resposta dele. Configurar a permissão do lado de quem chama não existe — e é o mal-entendido mais comum.
- 4 · A permissão libera LER, não ENVIAR. Sem ela, a requisição pode ter sido enviada e executada; o navegador apenas esconde a resposta. É por isso que a política não protege contra requisição forjada.
- 5 · O que de fato barra a requisição forjada é o cookie. O atributo que restringe o envio a requisições do mesmo site impede o navegador de anexar a credencial numa navegação vinda de fora. Essa é a defesa; a política de origem é outra coisa.
- 6 · Autorizar qualquer origem com credencial é proibido de propósito. A combinação seria equivalente a publicar a sessão do usuário. O navegador recusa — e quem tenta contorná-la está tentando desligar a proteção.
CSRF: ataques e defesas
# Cenário de ataque CSRF:
# 1. Vítima logada em bank.com (cookie de sessão ativo)
# 2. Vítima visita evil.com
# 3. evil.com tem: <form action="https://bank.com/transfer" method="POST">
# <input name="to" value="hacker_account">
# <input name="amount" value="10000">
# </form>
# <script>document.forms[0].submit();</script>
# 4. Browser envia o POST para bank.com COM O COOKIE DA VÍTIMA
# 5. bank.com processa a transferência como se fosse a vítima
# Defesa 1: SameSite Cookie (recomendado, mais simples)
# SameSite=Strict: cookie nunca enviado cross-site (nem em links externos)
# SameSite=Lax: cookie enviado em navegação top-level GET (links externos)
# MAS não em POST/PUT/DELETE cross-site → mitiga CSRF
# SameSite=None: enviado sempre (deve ter Secure obrigatoriamente)
from fastapi.responses import Response
import secrets
def set_session_cookie(response: Response, session_id: str):
"""Cookie de sessão com todos os atributos de segurança."""
response.set_cookie(
key="session_id",
value=session_id,
httponly=True, # JS não acessa
secure=True, # apenas HTTPS
samesite="lax", # mitiga CSRF, permite OAuth flows
max_age=86400, # 24h
path="/", # todo o site
# domain=".example.com", # subdomínios (use com cuidado)
)
# Defesa 2: CSRF Token (necessário quando SameSite não é suficiente)
from fastapi import Cookie, Form, HTTPException
def generate_csrf_token() -> str:
return secrets.token_urlsafe(32)
@app.post("/transfer")
async def transfer(
to: str = Form(...),
amount: float = Form(...),
csrf_token: str = Form(...), # token no body do form
session_csrf: str = Cookie(None), # mesmo token no cookie
):
# Valida que o token do form = token do cookie:
if not secrets.compare_digest(csrf_token, session_csrf):
raise HTTPException(status_code=403, detail="CSRF token inválido")
# Processa transferência...
# Defesa 3: Custom header (para SPAs com JSON API)
# Browser impede JS de adicionar headers customizados em requests cross-origin
# sem aprovação CORS — então se a API exige X-Requested-With, CSRF é impossível
@app.post("/api/transfer")
async def api_transfer(request: Request, ...):
if not request.headers.get("X-Requested-With"):
raise HTTPException(403, "Missing X-Requested-With header")
# Não precisa de CSRF token — custom header é proteção suficiente para JSON APIsQual é a diferença fundamental entre os ataques de origem cruzada e o mecanismo que restringe requisições entre origens?
Modelo mental: Same-Origin Policy protege de leitura cross-origin. CORS relaxa o SOP de forma controlada — nunca use *, apenas origens explícitas, especialmente com credentials. CSRF explora que browsers enviam cookies automaticamente em requests cross-site — mitigado com SameSite=Lax (default moderno) ou CSRF tokens. HttpOnly bloqueia roubo de cookie por XSS. Secure garante transmissão apenas em HTTPS. Use sempre os quatro juntos para cookies de sessão: HttpOnly + Secure + SameSite=Lax + Max-Age.
Trilha concluída! Próxima trilha: OSI e TCP/IP — volte ao início para revisar as camadas de rede.
Perguntas frequentes
❓ Por que a política de mesma origem existe?
❓ Quem autoriza uma requisição entre origens?
❓ Como proteger contra falsificação de requisição?
Fixando
Qual combinação de atributos de cookie oferece a melhor proteção de sessão?
Uma API é consumida apenas por aplicativos móveis e por um servidor. Ela precisa se preocupar com requisição forjada entre sites?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…