O que é CSRF: Entendendo o Ataque
CSRF (Cross-Site Request Forgery), também conhecido como XSRF, é um vetor de ataque onde um invasor força um usuário autenticado a realizar ações não intencionais em uma aplicação web. O ataque funciona porque o navegador envia automaticamente cookies de sessão junto com requisições, mesmo que o usuário tenha sido redirecionado para um site malicioso.
Imagine que você está autenticado no seu banco. Enquanto a sessão está ativa, você clica em um link suspeito que leva a um site criado por um atacante. Esse site pode conter uma requisição oculta que transfere dinheiro da sua conta bancária. Como seu navegador já possui o cookie de sessão válido do banco, a requisição é executada com sucesso, sem que você perceba. O banco processa a transferência porque a requisição veio com as credenciais válidas, mesmo que tenha sido originada de um domínio diferente.
Por que o navegador permite isso?
O navegador segue a política de mesmo domínio (same-origin policy) para leitura de dados, mas não para o envio. Requisições GET e POST de formulários tradicionais são permitidas entre domínios, pois foi assim que a web foi originalmente projetada. Isso criou uma brecha histórica que os atacantes exploram há décadas.
Impacto Real
O risco aumenta em aplicações que realizam ações sensíveis com requisições simples. Um banco que permite transferências via GET é uma vítima fácil. Uma rede social que permite publicações via POST tradicional sem proteção também está vulnerável. A severidade depende do que pode ser feito com uma requisição forjada — quanto mais crítica a ação, mais importante é a proteção.
SameSite Cookies: A Primeira Linha de Defesa Moderna
O atributo SameSite foi introduzido para limitar quando um cookie de sessão é enviado em requisições cross-site. Em vez de depender exclusivamente de tokens, o navegador agora pode recusar o envio automático do cookie se a requisição vier de um origem diferente.
O atributo SameSite possui três valores: Strict, Lax e None. Com Strict, o cookie nunca é enviado em requisições cross-site, mesmo em links normais — se você clicar em um link de um outro site para seu banco, seu cookie não será enviado, forçando novo login. Com Lax, o cookie é enviado apenas em requisições GET de navegação (como clicar em um link), mas não em requisições POST, PUT ou DELETE cross-site, oferecendo um equilíbrio entre segurança e usabilidade. Com None, o cookie é enviado sempre, mas requer o atributo Secure e funciona apenas em HTTPS.
Implementação em Node.js com Express
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'seu-segredo-aqui',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true, // Impede acesso via JavaScript
secure: true, // Apenas em HTTPS
sameSite: 'Strict', // Bloqueia requisições cross-site
maxAge: 3600000 // 1 hora em milissegundos
}
}));
app.post('/transferir', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ erro: 'Não autenticado' });
}
// Lógica de transferência
res.json({ sucesso: true, mensagem: 'Transferência realizada' });
});
app.listen(3000, () => console.log('Servidor rodando na porta 3000'));
Considerações Práticas sobre SameSite
O SameSite=Strict é o mais seguro, mas quebra funcionalidades legitimas como clicar em links de um email ou rede social que levam a seu aplicativo. O SameSite=Lax é recomendado para a maioria dos casos, protegendo contra CSRF em operações que modificam dados enquanto permite navegação normal. Para integração com serviços third-party, você pode precisar de SameSite=None; Secure, mas isso requer análise cuidadosa dos riscos.
Tokens Anti-CSRF: Proteção Ativa
Enquanto SameSite é uma defesa passiva no navegador, tokens anti-CSRF são uma proteção ativa implementada na aplicação. Um token CSRF é uma string aleatória e única gerada pelo servidor para cada sessão do usuário. O servidor armazena este token e o cliente deve incluí-lo em cada requisição que modifica dados. Como um atacante não consegue ler o token (due à same-origin policy), ele não pode forjar a requisição.
O fluxo é simples: o servidor gera um token único quando o usuário faz login ou acessa um formulário. O cliente o inclui em um campo oculto do formulário ou em um header customizado. Quando a requisição chega, o servidor compara o token enviado com o que está armazenado. Se não coincidirem ou se o token estiver ausente, a requisição é rejeitada.
Implementação em Node.js com Express e CSRF Middleware
const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: 'seu-segredo-aqui',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'Lax'
}
}));
// Middleware CSRF usando cookies
const csrfProtection = csrf({ cookie: false });
// Rota para exibir formulário
app.get('/formulario', csrfProtection, (req, res) => {
const token = req.csrfToken();
res.send(`
<form action="/transferir" method="POST">
<input type="hidden" name="_csrf" value="${token}">
<input type="text" name="valor" placeholder="Valor da transferência">
<button type="submit">Transferir</button>
</form>
`);
});
// Rota protegida
app.post('/transferir', csrfProtection, (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ erro: 'Não autenticado' });
}
// Token foi validado automaticamente pelo middleware
const valor = req.body.valor;
res.json({ sucesso: true, mensagem: `Transferência de ${valor} realizada` });
});
// Tratador de erros CSRF
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
res.status(403).json({ erro: 'Token CSRF inválido' });
} else {
next(err);
}
});
app.listen(3000);
Tokens em Requisições AJAX
Para aplicações modernas com APIs REST e requisições AJAX, o token é geralmente enviado em um header customizado em vez de um campo de formulário. O cliente obtém o token de um endpoint específico ou de um meta-tag no HTML inicial e o inclui em cada requisição.
// Cliente JavaScript
async function transferir(valor) {
const token = document.querySelector('meta[name="csrf-token"]').content;
const response = await fetch('/api/transferir', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token // Header customizado com o token
},
body: JSON.stringify({ valor: valor })
});
return response.json();
}
// Servidor validando o header
app.post('/api/transferir', csrfProtection, (req, res) => {
// O middleware valida automaticamente o header X-CSRF-Token
res.json({ sucesso: true });
});
Gerenciamento de Tokens
Tokens CSRF devem ser únicos por sessão ou por requisição, dependendo do nível de segurança desejado. Gerar um novo token a cada requisição é mais seguro mas impacta a performance e pode quebrar navegação com múltiplas abas. A abordagem padrão é usar um token por sessão, regenerado periodicamente ou quando a sessão é renovada. O token deve ter pelo menos 32 bytes de entropia aleatória e ser inacessível via JavaScript (armazenado apenas em cookies httpOnly ou sessão servidor).
Camadas de Proteção: Combinando as Defesas
A segurança máxima é alcançada usando tanto SameSite quanto tokens anti-CSRF simultaneamente. Eles funcionam em camadas diferentes e se complementam. SameSite bloqueia requisições cross-site antes mesmo do servidor processar a requisição, economizando recursos. Tokens anti-CSRF validam a legitimidade da requisição no servidor, funcionando como uma segunda barreira.
Considere este cenário: um usuário clica em um link malicioso. Se apenas SameSite=Lax estiver configurado, requisições GET passam mas POST é bloqueado. Porém, um atacante sofisticado pode usar JavaScript para fazer requisições AJAX com métodos POST (desde que não haja CORS configurado). Se você tiver tokens anti-CSRF, essa requisição será bloqueada no servidor porque o token está ausente ou inválido.
Checklist de Implementação
- Configure
SameSite=Lax(ouStrictse apropriado) em todos os cookies de sessão - Implemente geração e validação de tokens anti-CSRF em todas as operações que modificam dados
- Use
httpOnlyeSecureem todos os cookies sensíveis - Valide a origem da requisição quando apropriado (header
Referer) - Implemente rate limiting em endpoints sensíveis
- Eduque usuários sobre não clicar em links suspeitos e feche a sessão ao usar computadores públicos
Conclusão
Três pontos-chave devem ficar claros: primeiro, CSRF exploram a confiança automática que o navegador tem em cookies, enviando-os em qualquer requisição para um domínio, independentemente da origem. Segundo, SameSite cookies oferecem defesa passiva moderna diretamente no navegador, bloqueando envio de cookies em contextos cross-site, e deve ser considerado o padrão em qualquer aplicação nova. Terceiro, tokens anti-CSRF complementam essa proteção com validação ativa no servidor, sendo especialmente críticos em aplicações que precisam suportar múltiplos domínios ou serviços integrados.
A abordagem recomendada é usar SameSite=Lax como primeira linha de defesa (cobrindo a maioria dos cenários) e tokens anti-CSRF para operações sensíveis, criando uma arquitetura defensiva robusta. Nenhuma defesa é 100% infalível isoladamente, mas combinadas oferecem proteção prática contra a vasta maioria dos ataques CSRF no mundo real.