Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

Proteção CSRF em aplicações PHP

Como os programadores evitam o Cross-Site Request Forgery em aplicações PHP: tokens para cada pedido que altera dados, verificação da origem no cabeçalho e cookies SameSite como camada adicional.

Atualizado em 6 min de leituraDesenvolvimento seguro

Em resumo: Evita o CSRF em PHP exigindo que cada pedido que altere algo traga um token secreto, associado à sessão, que verifica no servidor com hash_equals(). Além disso, verifica os cabeçalhos Origin ou Sec-Fetch-Site e define os cookies de sessão com SameSite. Os pedidos GET nunca devem alterar dados, e o formulário de login também precisa de proteção.

Um clique que ninguém repara

Uma colaboradora tem sessão iniciada no backend da loja e abre, noutro separador, uma página externa. Essa página envia em segundo plano um formulário para o backend, por exemplo para alterar um endereço de correio eletrónico ou criar uma conta. O navegador anexa automaticamente o cookie de sessão. Para a aplicação, o pedido parece igual a qualquer outro da própria equipa.

Isto é o Cross-Site Request Forgery, abreviadamente CSRF: um pedido que um utilizador autenticado envia, sem o saber, a uma aplicação em que confia. O atacante não vê a resposta, mas também não precisa dela. Basta que a ação seja executada.

O CSRF não aparece nas manchetes como o PHP2Shell, mas é um achado frequente em aplicações PHP desenvolvidas à medida. Frameworks como o Symfony e o Laravel trazem uma proteção; as falhas surgem aí sobretudo onde ela foi desligada para rotas individuais. Os desenvolvimentos próprios sem framework não têm, muitas vezes, nenhuma.

CSRF em PHP: que pedidos têm de ser protegidos

Precisam de proteção todos os pedidos que alteram um estado: guardar, apagar, encomendar, atribuir permissões, alterar definições, carregar ficheiros, terminar sessão. Os acessos de leitura não precisam de token, desde que sejam realmente só de leitura.

Daqui resulta a primeira regra: nenhuma alteração por GET. Links como loeschen.php?id=… podem ser acionados com especial facilidade por páginas externas, por exemplo através de uma imagem incorporada. As ações que alteram dados pertencem a POST, PUT, PATCH ou DELETE.

A segunda regra: proteger também o início de sessão. No chamado CSRF de login, uma vítima é autenticada, sem dar por isso, numa conta do atacante. Se aí introduzir dados de pagamento ou documentos, estes vão parar ao atacante. Um token no formulário de login e um novo ID de sessão após o início de sessão bem-sucedido impedem-no.

O padrão do token sincronizador em PHP

Para aplicações clássicas com sessões, a OWASP recomenda o padrão do token sincronizador: o servidor gera um token aleatório, guarda-o na sessão e espera recebê-lo de volta em cada formulário que altera dados. É importante que o token seja enviado no formulário ou num cabeçalho próprio, não num cookie.

<?php
declare(strict_types=1);

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

function csrfPruefen(): void
{
    $gesendet = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!is_string($gesendet) || !hash_equals(csrfToken(), $gesendet)) {
        http_response_code(403);
        exit('Anfrage abgelehnt.');
    }
}

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    csrfPruefen();
}
?>
<form method="post" action="/konto/email">
    <input type="hidden" name="csrf_token"
           value="<?= htmlspecialchars(csrfToken(), ENT_QUOTES, 'UTF-8') ?>">
    <!-- outros campos -->
</form>

O hash_equals() compara em tempo constante, de modo que o tempo de resposta nada revela sobre o valor correto. Após o início de sessão, deve renovar o ID de sessão com session_regenerate_id(true) e gerar um novo token. Porque é que o random_bytes() é a fonte certa e que funções são inadequadas é explicado em Números aleatórios e tokens seguros em PHP.

Um token por sessão basta para a maioria das aplicações. Tokens por formulário ou por pedido aumentam um pouco a proteção, mas levam a problemas com vários separadores abertos e com o botão Voltar. Mais importante do que a frequência da mudança é que o token nunca apareça em URLs, onde pode ir parar a registos, ao histórico do navegador e a indicações de referrer para páginas externas.

Para aplicações sem estado e sem sessão, a OWASP indica como alternativa o cookie double-submit assinado: um token associado à sessão por HMAC. A variante simples, não assinada, não é expressamente recomendada.

Segunda camada: Origin, Sec-Fetch-Site e SameSite

Os navegadores modernos enviam, nos pedidos, informações sobre a sua origem. O cabeçalho Origin indica a página de origem do pedido, e o Sec-Fetch-Site diz se o pedido vem da mesma página (same-origin), de uma relacionada (same-site) ou de uma externa (cross-site). Daqui pode construir-se uma verificação adicional simples:

<?php
$erlaubteHerkunft = 'https://www.example.de';
$fetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? null;
$origin    = $_SERVER['HTTP_ORIGIN'] ?? null;

$verdaechtig = ($fetchSite !== null && $fetchSite !== 'same-origin')
    || ($fetchSite === null && $origin !== null && $origin !== $erlaubteHerkunft);

if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $verdaechtig) {
    http_response_code(403);
    exit;
}

A OWASP sublinha dois pontos: a origem tem de ser comparada por inteiro, para que domínios de nome semelhante não passem, e a ausência do cabeçalho ou o valor null não pode contar como origem própria. Como os navegadores mais antigos nem sempre enviam os cabeçalhos, o token continua a ser a defesa principal.

A terceira camada são os cookies de sessão com SameSite. O PHP suporta session.cookie_samesite desde a versão 7.3; o valor por defeito é vazio. Defina pelo menos Lax; para backends puros, Strict é muitas vezes possível. As restantes definições de cookies são descritas no guia Proteger sessões em PHP.

Se usar um CMS, este traz normalmente ferramentas próprias. O WordPress, por exemplo, trabalha com os chamados nonces, que os plugins integram através de funções como wp_nonce_field() e check_admin_referer(). Na prática, as falhas surgem sobretudo em extensões que se esquecem dessas verificações em ações individuais. Verifique por isso especialmente os plugins escritos por si ou pouco divulgados.

Em interfaces que os navegadores usam a partir de outros domínios, decide também a configuração CORS. Se o servidor devolver qualquer origem solicitante como permitida e permitir ao mesmo tempo cookies, anula ele próprio a proteção do navegador. A OWASP recomenda abrir o CORS apenas para origens expressamente de confiança; uma autorização global de todos os subdomínios torna-se um problema assim que um deles cair em mãos alheias.

Lista de verificação para aplicações existentes

Em projetos que cresceram ao longo do tempo, ajuda uma revisão sistemática:

  1. Registar todas as rotas e scripts que alteram dados, incluindo endpoints Ajax e uploads de ficheiros.
  2. Verificar se para isso é usado GET e migrar para POST ou métodos adequados.
  3. Para cada rota que altera dados, comprovar que um token é verificado; procurar exceções no framework de forma dirigida.
  4. Testar separadamente o início de sessão, o fim de sessão e a alteração da palavra-passe.
  5. Acrescentar session.cookie_samesite e a verificação da origem.
  6. Corrigir as falhas XSS, pois o código introduzido pode ler qualquer token. O procedimento é descrito em Evitar XSS em PHP.
Importante

Uma proteção CSRF verificada apenas no frontend, por JavaScript, é ineficaz. A verificação tem de ocorrer no servidor, antes de a ação ser executada.

Como ajuda concretamente a PHP SOS?

  • Registamos todas as rotas que alteram dados na sua aplicação PHP e verificamos onde falta um token ou onde foi desligado.
  • Montamos um ponto central de verificação, em vez de distribuir as verificações de token por muitos scripts.
  • Acrescentamos verificação da origem e definições SameSite, sem perturbar os fluxos de pagamento ou de início de sessão.
  • Testamos de novo após a migração e documentamos os resultados.

Uma verificação completa da sua aplicação é descrita no serviço Auditoria de segurança e teste de intrusão.

Perguntas frequentes

O SameSite=Lax chega como proteção CSRF?

Não. Segundo a OWASP, os cookies SameSite são uma camada adicional, não um substituto. O Lax continua a enviar cookies em acessos normais a páginas por GET, e os ataques a partir de subdomínios do mesmo site não são cobertos.

As APIs JSON precisam de um token CSRF?

Se a API autenticar com cookies, sim, ou uma verificação equivalente. Uma alternativa são cabeçalhos de pedido próprios, que os navegadores só permitem a partir de páginas externas após uma verificação prévia CORS. Mas então a autorização CORS só pode incluir origens expressamente de confiança.

O formulário de início de sessão também tem de ser protegido?

Sim. No CSRF de login, uma vítima é autenticada sem dar por isso numa conta alheia e introduz aí os seus próprios dados. Um token no formulário de login e uma nova sessão após o início de sessão impedem-no.

A proteção CSRF protege também contra XSS?

É ao contrário: uma falha XSS anula qualquer proteção CSRF, porque o código introduzido pode, em nome da página, ler tokens válidos e enviá-los. Ambas as vulnerabilidades têm de ser corrigidas separadamente.

Fontes

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata