Prevenindo CSRF
Esta aula aborda a prevenção de ataques CSRF (Cross-Site Request Forgery) em aplicações PHP, explicando o conceito, a implementação de tokens CSRF, o uso de cookies SameSite e a implementação prática de proteção em formulários e requisições.
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 AJAXAlé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
- OWASP - Cross-Site Request Forgery (CSRF)
- MDN - Cookies SameSite
- PHP Manual - hash_equals
- PHP Manual - setcookie
- OWASP CSRF Prevention Cheat Sheet
- PortSwigger - CSRF (Web Security Academy)
Exercícios
- Explique com suas palavras o que é CSRF e por que é perigoso. Dê um exemplo de ataque em uma aplicação PHP.
- 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. - Configure um cookie de sessão com o atributo SameSite=Lax usando
session_set_cookie_params(). Explique a diferença entre Lax e Strict. - Descreva como você protegeria uma requisição AJAX contra CSRF, incluindo código JavaScript e PHP.
- Discuta as limitações dos tokens CSRF e como o SameSite cookies complementa essa proteção. Cite cenários onde cada um é mais eficaz.
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 '
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.
// 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; }