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:

  1. O usuário faz login no site legítimo (ex.: banco.com)
  2. O navegador armazena o cookie de sessão
  3. O usuário visita um site malicioso sem saber
  4. 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