CSRF (Cross-Site Request Forgery) é uma vulnerabilidade de segurança que permite a um atacante induzir um usuário autenticado a executar ações indesejadas em uma aplicação web. Nesta aula, vamos entender como o CSRF funciona, por que é perigoso e, principalmente, como prevenir esse tipo de ataque em aplicações PHP. Você aprenderá sobre tokens CSRF, cookies SameSite e como implementar essas proteções de forma eficaz.

Vamos explorar desde os conceitos básicos até a implementação prática, com exemplos de código que você pode adaptar aos seus projetos. Ao final, você terá as ferramentas necessárias para proteger suas aplicações contra esse tipo de ataque.

O que é CSRF

CSRF, ou Cross-Site Request Forgery, é um tipo de ataque em que um site malicioso faz com que o navegador de um usuário autenticado envie requisições não intencionais para um site legítimo. O atacante explora a confiança que o site legítimo tem no navegador do usuário, aproveitando-se do fato de que cookies de autenticação são enviados automaticamente em cada requisição.

Por exemplo, imagine que você está logado em um site de banco. Se um atacante conseguir que você visite uma página maliciosa que contenha um formulário ou uma imagem que envia uma requisição para o banco, o navegador incluirá seus cookies automaticamente. Se o banco não tiver proteção CSRF, a requisição será processada como se fosse legítima, permitindo que o atacante realize ações como transferir dinheiro, alterar senhas ou modificar dados.

O CSRF é diferente de XSS (Cross-Site Scripting), pois no XSS o atacante injeta código no próprio site, enquanto no CSRF o atacante usa o navegador da vítima para enviar requisições forjadas. A prevenção do CSRF é essencial para garantir a integridade das ações do usuário.

Uma analogia comum: é como se alguém assinasse um cheque em seu nome sem você saber, usando sua assinatura (cookies) para validar a transação. A proteção CSRF garante que o site só aceite requisições que você realmente intencionou fazer.

Tokens CSRF

O método mais comum e eficaz de prevenção de CSRF é o uso de tokens CSRF. Um token CSRF é um valor aleatório e único que é gerado pelo servidor e incluído em formulários ou cabeçalhos de requisição. Quando o usuário submete o formulário, o servidor verifica se o token enviado corresponde ao token associado à sessão do usuário. Se não corresponder, a requisição é rejeitada.

Os tokens devem ser imprevisíveis, únicos por sessão e expirar após um período de tempo. Eles podem ser armazenados na sessão do usuário (geralmente em $_SESSION) e devem ser incluídos em cada formulário que realize ações sensíveis (como alterar dados, fazer upload, excluir, etc.).

A implementação básica envolve:

  • Gerar um token aleatório usando random_bytes() ou bin2hex().
  • Armazenar o token na sessão.
  • Incluir o token como um campo oculto no formulário.
  • No processamento do formulário, comparar o token recebido com o armazenado.

Exemplo de geração e inclusão em um formulário:

session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['csrf_token'];
?>

No processamento, você verifica:

session_start();
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
    die('Token CSRF inválido');
}
// Processa a requisição

Usar hash_equals é importante para evitar ataques de timing, pois compara strings em tempo constante.

SameSite cookies

Os cookies SameSite são uma camada adicional de proteção contra CSRF. A diretiva SameSite do cookie controla quando o navegador deve enviar o cookie em requisições cross-site. Existem três valores possíveis:

  • Strict: o cookie é enviado apenas em requisições de primeira parte (mesmo site). Isso oferece a maior proteção, mas pode prejudicar a experiência do usuário em casos de navegação entre sites (ex.: links externos).
  • Lax: o cookie é enviado em requisições de primeira parte e em requisições de navegação top-level (como clicar em um link), mas não em requisições de sub-recursos (como imagens ou iframes). É um bom equilíbrio entre segurança e usabilidade.
  • None: o cookie é enviado em todas as requisições, inclusive cross-site. Deve ser usado apenas com o atributo Secure (HTTPS).

Para definir o atributo SameSite em PHP, você pode usar a função setcookie() ou a classe Cookie (se estiver usando um framework). Exemplo:

setcookie('session_id', $sessionId, [
    'expires' => time() + 3600,
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

É importante notar que o atributo SameSite é suportado pela maioria dos navegadores modernos, mas não é uma solução completa por si só. Recomenda-se usar tokens CSRF em conjunto com SameSite para uma proteção mais robusta.

Implementação

Vamos implementar uma proteção CSRF completa em uma aplicação PHP. Primeiro, criaremos uma função para gerar e verificar tokens. Em seguida, aplicaremos em um formulário de exemplo.

Passo 1: Funções auxiliares

function gerarTokenCSRF() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function verificarTokenCSRF($token) {
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

Passo 2: Incluir o token em um formulário

session_start();
$token = gerarTokenCSRF();
?>

Passo 3: Verificar o token no processamento

session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_POST['csrf_token']) || !verificarTokenCSRF($_POST['csrf_token'])) {
        http_response_code(403);
        die('Falha na verificação CSRF');
    }
    // Processa os dados
    $nome = $_POST['nome'];
    // ...
}

Passo 4: Configurar cookies com SameSite

Ao criar cookies de sessão, use a configuração adequada:

session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();

Essa configuração deve ser feita antes de session_start().

Considerações adicionais

  • Use HTTPS em produção para garantir que os cookies com Secure funcionem.
  • Renove o token CSRF periodicamente ou após ações sensíveis (ex.: login).
  • Para APIs, use tokens CSRF em cabeçalhos (ex.: X-CSRF-Token) e valide com Middleware.

Uma implementação mais robusta pode incluir a rotação de tokens a cada requisição, mas isso pode causar problemas com o botão voltar do navegador. Uma abordagem comum é manter o token por sessão, mas usar um token duplo (um no formulário e um em cookie) para maior segurança.

Boas práticas

Além do que já vimos, aqui estão algumas boas práticas para prevenir CSRF:

  • Use tokens CSRF em todas as requisições que alteram estado (POST, PUT, DELETE).
  • Nunca use tokens CSRF em requisições GET, pois podem vazar em logs ou histórico.
  • Combine tokens CSRF com SameSite cookies e outras medidas como validação de origem (Origin/Referer check).
  • Mantenha as bibliotecas e frameworks atualizados, pois eles frequentemente incluem proteções CSRF embutidas (ex.: Laravel, Symfony).
  • Eduque os usuários sobre os riscos de clicar em links suspeitos, embora isso não seja uma medida técnica.

A defesa em profundidade é essencial: não confie apenas em uma técnica, mas combine várias camadas de segurança.

Referências

Exercícios

  1. Explique o que é CSRF e dê um exemplo de ataque em um site de compras.

✓ Resposta: CSRF é uma vulnerabilidade que permite a um atacante induzir um usuário autenticado a executar ações não intencionais. Por exemplo, em um site de compras, o atacante pode criar uma página que envia uma requisição para adicionar um item ao carrinho ou alterar o endereço de entrega, aproveitando os cookies de sessão do usuário.
  1. Como os tokens CSRF funcionam? Descreva o fluxo de geração e verificação.

✓ Resposta: Um token aleatório é gerado no servidor e armazenado na sessão. Ele é incluído em formulários como campo oculto. Quando o formulário é submetido, o servidor compara o token recebido com o armazenado usando hash_equals. Se coincidirem, a requisição é processada; caso contrário, é rejeitada.
  1. O que é a diretiva SameSite e quais são os valores possíveis? Dê um exemplo de configuração em PHP.

✓ Resposta: SameSite controla o envio de cookies em requisições cross-site. Valores: Strict, Lax e None. Exemplo: setcookie('session', $id, ['samesite' => 'Lax', 'secure' => true]);
  1. Implemente uma função de verificação CSRF que utilize hash_equals e explique por que essa função é recomendada.

✓ Resposta: function verificarTokenCSRF($token) { return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } hash_equals é recomendada porque compara strings em tempo constante, evitando ataques de timing que podem revelar informações sobre o token.
  1. Cite três boas práticas adicionais para prevenir CSRF além das já mencionadas.

✓ Resposta: 1) Validar o cabeçalho Origin/Referer; 2) Usar tokens CSRF em cabeçalhos personalizados para APIs; 3) Utilizar frameworks com proteção CSRF embutida e mantê-los atualizados.