Guia Completo de CSRF: Mecanismo de Ataque, SameSite Cookies e Tokens Anti-CSRF Já leu

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

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 (ou Strict se 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 httpOnly e Secure em 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.


Referências


Artigos relacionados