CSRF (Cross-Site Request Forgery) é um tipo de ataque onde um usuário autenticado é induzido a executar ações indesejadas em uma aplicação web, como alterar senha, fazer transferências bancárias ou modificar configurações. O ataque explora a confiança que o servidor tem no navegador do usuário, enviando requisições forjadas que carregam automaticamente cookies de sessão. Nesta aula, vamos explorar o conceito de CSRF, as principais técnicas de prevenção e como implementá-las em aplicações PHP, garantindo maior segurança para seus usuários.

Dominar a prevenção de CSRF é essencial para qualquer desenvolvedor web que deseja criar aplicações robustas e confiáveis. Veremos desde os fundamentos até a implementação prática com tokens CSRF e cookies SameSite, além de boas práticas para integrar essas defesas em seu código.

O que é

Cross-Site Request Forgery (CSRF), também conhecido como "sea surf", é uma vulnerabilidade de segurança que permite a um atacante forjar requisições HTTP em nome de uma vítima autenticada. O ataque ocorre quando uma aplicação web confia em requisições sem verificar se elas foram intencionalmente enviadas pelo usuário. Um cenário típico: o usuário está logado em um site bancário e, em outra aba, acessa um site malicioso que contém um formulário ou script que envia uma requisição para o site bancário, aproveitando os cookies de sessão do usuário. Como o servidor não consegue distinguir a origem da requisição, ele processa a ação como legítima.

Os ataques CSRF são especialmente perigosos porque podem ser realizados sem nenhuma interação visível do usuário, muitas vezes através de tags <img> ou <script> que carregam URLs automaticamente. Para mitigar esse risco, é fundamental implementar mecanismos de proteção que verifiquem a intenção do usuário, como tokens CSRF e cookies SameSite.

Tokens CSRF

Tokens CSRF são valores aleatórios e únicos gerados pelo servidor e associados à sessão do usuário. Eles são incluídos em formulários e requisições sensíveis, e o servidor valida se o token recebido corresponde ao armazenado na sessão. Dessa forma, qualquer requisição que não contenha o token correto é rejeitada, bloqueando ataques CSRF.

A implementação típica envolve gerar um token na criação da sessão ou na renderização do formulário, armazená-lo na variável de sessão e inseri-lo como um campo oculto no formulário. Ao receber a submissão, o servidor compara o token enviado com o da sessão. Se forem iguais, a requisição é processada; caso contrário, é abortada. É importante que o token seja imprevisível e que a comparação seja feita de forma segura (por exemplo, usando hash_equals para evitar ataques de timing).

Exemplo de geração de token em PHP:

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

Inclusão em formulário:

Validação no servidor:

session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        die('Token CSRF inválido.');
    }
    // Processar requisição
}

SameSite cookies

O atributo SameSite dos cookies é uma camada adicional de proteção contra CSRF. Ele instrui o navegador a enviar o cookie apenas em requisições do mesmo site (origem), bloqueando envios em requisições cross-site. Existem três valores possíveis: Strict, Lax e None. O valor Strict impede o envio do cookie em qualquer requisição cross-site, mas pode quebrar navegações comuns (como clicar em um link externo). O valor Lax permite o envio em navegações de topo (por exemplo, clicar em um link), mas bloqueia em submissões de formulários e requisições AJAX. O valor None desativa a proteção, mas requer o atributo Secure (HTTPS).

Para configurar o SameSite em PHP, você pode usar a função setcookie() com o parâmetro samesite, disponível a partir do PHP 7.3. Em versões anteriores, é necessário usar a função header() para definir o cookie manualmente. A configuração recomendada para a maioria das aplicações é Lax, pois equilibra segurança e usabilidade.

Exemplo com setcookie():

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

Para versões anteriores ao PHP 7.3:

header('Set-Cookie: session_cookie=' . $session_id . '; Path=/; Secure; HttpOnly; SameSite=Lax');

Implementação

Vamos implementar uma proteção CSRF completa em uma aplicação PHP simples. Criaremos funções para gerar e validar tokens, integraremos em formulários e também em requisições AJAX. Além disso, configuraremos cookies com SameSite para reforçar a segurança.

Primeiro, criamos um arquivo csrf.php com funções reutilizáveis:

// csrf.php
session_start();

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

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

function csrfField() {
    $token = generateCsrfToken();
    return '';
}

Em um formulário, incluímos o campo oculto:

require 'csrf.php';
?>

No processamento, validamos o token:

require 'csrf.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_POST['csrf_token']) || !validateCsrfToken($_POST['csrf_token'])) {
        http_response_code(403);
        die('Falha na validação CSRF.');
    }
    // Processar dados
}

Para requisições AJAX, podemos enviar o token via cabeçalho HTTP ou campo de dados. Exemplo com JavaScript e fetch:

// No HTML, após a inclusão do csrf.php
?>

No servidor, validamos o cabeçalho:

require 'csrf.php';
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!validateCsrfToken($token)) {
    http_response_code(403);
    exit('Token inválido');
}
// Processar requisição AJAX

Além disso, configure os cookies de sessão com SameSite. Isso pode ser feito no início do script ou no arquivo de configuração do PHP (php.ini) via session.cookie_samesite. Exemplo em código:

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

Lembre-se de que a proteção CSRF deve ser aplicada a todas as rotas que alteram estado (POST, PUT, DELETE), e também a rotas que retornam dados sensíveis, embora o foco principal seja em mutações. A combinação de tokens CSRF e SameSite cookies oferece uma defesa em profundidade.

Boas práticas e observações finais

Além das técnicas mencionadas, é importante renovar o token CSRF após o login ou logout, e considerá-lo expirado após um período de inatividade. Também é crucial usar HTTPS para proteger a transmissão de tokens e cookies. Evite expor o token em URLs, pois isso pode vazar em logs e referrer headers. Sempre valide o token usando hash_equals() para evitar ataques de timing. Em aplicações com múltiplas abas, lembre-se de que o token é compartilhado na sessão, então não há problema. Para requisições AJAX, é comum usar o cabeçalho X-CSRF-Token ou incluir o token no corpo da requisição.

Teste sua aplicação para garantir que a proteção não interfira na experiência do usuário, especialmente em navegação com links externos. O uso de SameSite=Lax é um bom compromisso. Para aplicações que exigem integrações cross-site (como APIs públicas), você pode usar CORS e tokens de autenticação próprios, mas nunca confie apenas em cookies.

Referências

Exercícios

  1. Explique com suas palavras o que é CSRF e por que é perigoso. Dê um exemplo de ataque em uma aplicação PHP.
  2. Implemente uma função em PHP que gere um token CSRF e outra que valide um token recebido, utilizando hash_equals(). Mostre como usá-las em um formulário simples.
  3. Configure um cookie de sessão com o atributo SameSite=Lax usando session_set_cookie_params(). Explique a diferença entre Lax e Strict.
  4. Descreva como você protegeria uma requisição AJAX contra CSRF, incluindo código JavaScript e PHP.
  5. Discuta as limitações dos tokens CSRF e como o SameSite cookies complementa essa proteção. Cite cenários onde cada um é mais eficaz.

✓ Resposta: CSRF (Cross-Site Request Forgery) é um ataque onde um usuário autenticado é forçado a executar ações indesejadas sem seu consentimento. O perigo reside no fato de que o servidor confia na requisição, pois ela contém os cookies de sessão do usuário. Exemplo: um site malicioso contém uma imagem com o URL de um endpoint de transferência bancária. Quando o usuário, logado no banco, visita o site malicioso, o navegador carrega a URL e envia a requisição com os cookies, efetuando a transferência sem o conhecimento do usuário.

✓ Resposta:
function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

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

// Uso em formulário:
session_start();
$token = generateCsrfToken();
echo '
...'; // Validação: if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!validateCsrfToken($_POST['csrf_token'])) { die('Inválido'); } }

✓ Resposta:
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();
Lax permite o envio do cookie em navegações de topo (como clicar em um link), mas bloqueia em submissões de formulários e AJAX cross-site. Strict bloqueia todos os envios cross-site, mas pode quebrar navegação normal. Lax é mais amigável.

✓ Resposta: Para proteger AJAX, envie o token via cabeçalho personalizado (ex: X-CSRF-Token). No JavaScript, leia o token de uma meta tag ou variável PHP e inclua no cabeçalho. No PHP, valide o cabeçalho com hash_equals. Exemplo:
// PHP: gerar token e passar para JS
$token = generateCsrfToken();
echo '';

// JavaScript
fetch('processar.php', {
    method: 'POST',
    headers: { 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content },
    body: data
});

// PHP: validar
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!validateCsrfToken($token)) { http_response_code(403); exit; }

✓ Resposta: Tokens CSRF são eficazes para proteger formulários e requisições que exigem intenção do usuário, mas podem ser contornados se o atacante conseguir obter o token (ex: via XSS). SameSite cookies protegem contra requisições cross-site automáticas, mas não protegem contra subdomínios comprometidos ou ataques de mesma origem. A combinação oferece defesa em profundidade: tokens protegem contra requisições forjadas mesmo se o cookie for enviado, e SameSite bloqueia o envio do cookie em contextos cross-site, reduzindo a superfície de ataque. Tokens são mais eficazes em aplicações com formulários e AJAX; SameSite é especialmente útil para proteger cookies de sessão em navegação.