Cross-Site Request Forgery (CSRF): proteção em APIs e formulários
1. O que é CSRF e por que ele é perigoso?
Cross-Site Request Forgery (CSRF) é uma vulnerabilidade de segurança que força um usuário autenticado a executar ações indesejadas em uma aplicação web. O ataque explora a confiança que o servidor deposita no navegador da vítima, utilizando cookies ou credenciais armazenadas para realizar requisições legítimas sem o consentimento do usuário.
O perigo real do CSRF está na sua capacidade de executar ações críticas sem que a vítima perceba. Um atacante pode forçar a alteração de senha, realizar transferências financeiras, excluir dados sensíveis ou modificar configurações de conta. Como a requisição parte do navegador autenticado, o servidor a trata como uma ação legítima.
2. Como funciona um ataque CSRF?
O fluxo típico de um ataque CSRF segue estas etapas:
- O usuário faz login no site legítimo (ex.: banco.com)
- O navegador armazena o cookie de sessão
- O usuário visita um site malicioso sem saber
- O site malicioso dispara requisições para banco.com utilizando o cookie armazenado
Exemplo de ataque via GET:
<img src="https://banco.com/transferir?valor=1000&destino=atacante" style="display:none">
Quando o navegador carrega a imagem, ele faz uma requisição GET para o banco com os parâmetros maliciosos. Se o servidor processar ações via GET, a transferência é executada.
Exemplo de ataque via POST com formulário oculto:
<form action="https://banco.com/transferir" method="POST" id="ataque">
<input type="hidden" name="valor" value="1000">
<input type="hidden" name="destino" value="atacante">
</form>
<script>document.getElementById('ataque').submit();</script>
3. Diferenças entre CSRF e XSS
Embora ambos sejam ataques no navegador, CSRF e XSS exploram confianças diferentes:
CSRF explora a confiança do site no navegador do usuário. O atacante não precisa ver o conteúdo da resposta, apenas forjar requisições que o servidor aceitará como legítimas.
XSS (Cross-Site Scripting) explora a confiança do usuário no site. O atacante injeta scripts maliciosos que serão executados no contexto da aplicação legítima.
Combinação perigosa: Um ataque XSS pode roubar tokens CSRF, permitindo que o atacante realize requisições mesmo com proteção implementada. Por isso, proteger contra XSS é fundamental para uma defesa robusta contra CSRF.
4. Proteção em formulários web tradicionais
A proteção mais comum para formulários web é o uso de tokens CSRF sincronizados. O servidor gera um token único por sessão e o insere como campo oculto no formulário. Ao receber a requisição, o servidor valida se o token enviado corresponde ao armazenado na sessão.
Implementação no servidor (geração do token):
// Geração do token CSRF na sessão
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
Formulário HTML com token oculto:
<form action="/alterar-senha" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>">
<input type="password" name="nova_senha">
<button type="submit">Alterar Senha</button>
</form>
Validação no servidor:
// Validação do token CSRF
session_start();
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("Erro: Token CSRF inválido");
}
// Processa a requisição
Boas práticas:
- Renovar o token após login bem-sucedido
- Não expor tokens em logs ou URLs
- Invalidar tokens após logout
- Utilizar tokens com tempo de expiração
5. Proteção em APIs RESTful
APIs RESTful apresentam desafios específicos para proteção CSRF, especialmente quando são stateless e utilizam tokens JWT.
Uso de cabeçalho personalizado (X-CSRF-Token):
O cliente envia o token CSRF em um cabeçalho HTTP personalizado. Como requisições cross-origin não podem definir cabeçalhos personalizados sem permissão CORS, isso bloqueia ataques básicos.
Implementação com JavaScript:
// Cliente enviando token CSRF no cabeçalho
fetch('/api/transferir', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ valor: 1000, destino: 'conta123' })
});
Double Submit Cookie Pattern:
O token CSRF é enviado tanto como cookie quanto como cabeçalho personalizado. O servidor verifica se ambos correspondem. Esse padrão funciona bem com APIs stateless.
// Servidor define cookie CSRF
Set-Cookie: csrf_token=abc123; SameSite=Strict; Secure
// Cliente envia token no cabeçalho
X-CSRF-Token: abc123
SameSite Cookies:
A configuração SameSite adiciona proteção em nível de navegador:
- Strict: Cookie nunca enviado em requisições cross-site
- Lax: Cookie enviado em navegação top-level (links GET)
- None: Cookie sempre enviado (requer Secure)
Para APIs, SameSite=Strict oferece a melhor proteção, mas pode quebrar funcionalidades que dependem de redirecionamentos.
6. Estratégias complementares de defesa
Verificação de cabeçalhos Origin e Referer:
O servidor pode validar se a requisição veio de uma origem esperada.
// Validação do cabeçalho Origin
$allowedOrigins = ['https://meusite.com'];
if (!in_array($_SERVER['HTTP_ORIGIN'], $allowedOrigins)) {
http_response_code(403);
die("Origem não autorizada");
}
Reautenticação para ações críticas:
Para operações sensíveis (transferências, exclusão de conta), solicite a senha atual ou um segundo fator de autenticação.
CAPTCHA em operações sensíveis:
Adicionar CAPTCHA em formulários críticos impede automação de ataques.
Limitação de métodos HTTP:
APIs devem usar GET apenas para leitura e POST/PUT/DELETE para ações que alteram estado.
7. Erros comuns e armadilhas na implementação
Token não vinculado à sessão do usuário:
Se o token CSRF não estiver associado a uma sessão específica, um atacante pode reutilizar o mesmo token para múltiplos usuários.
Token exposto em parâmetros GET:
Nunca envie tokens CSRF via URL, pois eles podem vazar em logs, referenciadores ou bookmarks.
Uso inadequado de CORS:
Configurações CORS permissivas (Access-Control-Allow-Origin: *) anulam a proteção de cabeçalhos personalizados.
Ignorar proteção em subdomínios:
Subdomínios podem definir cookies para o domínio pai. Um XSS em subdomínio compromete a proteção CSRF do domínio principal.
Não proteger endpoints que aceitam múltiplos content-types:
Se uma API aceita tanto JSON quanto form-urlencoded, o atacante pode enviar o payload em formato diferente para contornar validações.
// Exemplo de endpoint vulnerável que aceita múltiplos content-types
POST /api/transferir
Content-Type: application/x-www-form-urlencoded
valor=1000&destino=atacante
Conclusão
Proteger aplicações contra CSRF exige uma abordagem em camadas. Tokens sincronizados, cabeçalhos personalizados, SameSite cookies e validação de origem formam uma defesa robusta. A escolha da estratégia depende do tipo de aplicação (formulários tradicionais vs. APIs RESTful) e dos requisitos de usabilidade. Lembre-se: nenhuma proteção isolada é suficiente; combine múltiplas técnicas e mantenha-se atualizado sobre novas vulnerabilidades.
Referências
-
OWASP Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet — Guia completo da OWASP com todas as estratégias de prevenção CSRF, incluindo exemplos de código e boas práticas.
-
Mozilla Developer Network: SameSite cookies explained — Documentação oficial sobre a configuração SameSite para cookies, com exemplos de uso e comportamento em diferentes navegadores.
-
PortSwigger Web Security Academy: CSRF — Tutorial interativo do Burp Suite sobre CSRF, com laboratórios práticos para testar vulnerabilidades e defesas.
-
CWE-352: Cross-Site Request Forgery (CSRF) — Definição oficial da vulnerabilidade no catálogo Common Weakness Enumeration, com descrição técnica e exemplos.
-
Google Web Fundamentals: Security - CSRF — Guia do Google sobre proteção CSRF em aplicações web modernas, incluindo estratégias para SPAs e APIs.
-
IETF RFC 7231: HTTP/1.1 Semantics and Content — Especificação oficial dos métodos HTTP e suas propriedades de segurança, fundamental para entender limitação de métodos contra CSRF.
-
Auth0 Blog: CSRF Protection in Modern Web Applications — Artigo técnico sobre implementação de proteção CSRF em aplicações modernas com React, Angular e APIs RESTful.