Uma landing page criada por IA pode enviar um formulário autenticado com poucas linhas de código. Se o navegador anexa cookies automaticamente, outra página pode tentar induzir a pessoa autenticada a disparar a mesma ação sem perceber. O formulário parece correto, o endpoint reconhece a sessão e justamente essa combinação abre espaço para Cross-Site Request Forgery, ou CSRF.

Proteger formulários contra CSRF é provar, no servidor, que uma requisição de mudança veio de um fluxo autorizado. A correção não é esconder a URL da API, adicionar uma validação apenas no JavaScript ou confiar que CORS bloqueará tudo. O endpoint precisa rejeitar requisições que tenham sessão válida, mas não apresentem a evidência esperada de intenção e contexto.

Este guia ajuda a localizar o risco, escolher controles compatíveis com a arquitetura e testar a proteção antes e depois da publicação.

Descubra se o fluxo está exposto a CSRF

CSRF exige uma combinação importante: o navegador envia uma credencial automaticamente e o servidor aceita uma ação com essa credencial. Procure no projeto:

Terminal window
rg -n \
'<form|fetch\(|XMLHttpRequest|axios|credentials:|withCredentials|method=.*post|action=' \
src public

Para cada fluxo, registre:

ação:
endpoint:
método HTTP:
muda estado:
credencial:
cookie enviado automaticamente:
origem da landing:
origem da API:
proteção CSRF atual:
efeito de repetição:

Priorize ações como:

  • alterar email, senha ou preferências;
  • conectar uma conta;
  • criar pedido, assinatura ou agendamento;
  • enviar convite;
  • excluir ou publicar conteúdo;
  • trocar domínio ou configuração;
  • registrar qualquer mudança vinculada à sessão da pessoa.

Um formulário público de contato sem sessão automática pode enfrentar spam, abuso e validação insuficiente, mas não necessariamente CSRF. Não aplique um mecanismo por nome sem confirmar o modelo de credencial e ameaça.

O artigo de testes de formulário cobre validação, acessibilidade, consentimento e entrega. Aqui o foco é impedir uma ação autenticada forjada.

Corrija primeiro os métodos e a autorização

Requisições GET, HEAD e OPTIONS não deveriam alterar estado. Se abrir uma URL confirma email, remove um recurso ou muda uma configuração sem uma etapa protegida, corrija esse contrato antes de adicionar tokens.

No servidor, cada operação precisa validar:

  1. identidade da sessão;
  2. autorização para o recurso;
  3. método e formato aceitos;
  4. evidência de proteção CSRF;
  5. limites de repetição quando a ação não for idempotente.

CSRF não substitui autorização. Um token válido prova que a requisição passou pelo fluxo esperado; ele não prova que a pessoa pode editar qualquer objeto indicado no corpo.

Também não confie em campo escondido como userId para escolher a conta. Associe a ação à identidade validada no servidor.

Use token sincronizado em aplicações com sessão

No padrão de synchronizer token, o servidor gera um valor secreto, imprevisível e associado à sessão. O frontend o devolve em cada ação protegida, e o servidor compara os valores antes de executar a mudança.

Um formulário renderizado pelo servidor pode receber o token como campo:

<form method="post" action="/conta/email">
<input type="hidden" name="csrf_token" value="VALOR_GERADO_PELO_SERVIDOR" />
<!-- campos da ação -->
</form>

O valor ilustrativo não deve ser gravado no repositório nem gerado durante a build estática. Em uma aplicação real:

  • o servidor cria o token com fonte segura;
  • o token é vinculado à sessão ou à requisição;
  • o endpoint compara os valores de forma segura;
  • ausência, expiração ou divergência resulta em rejeição;
  • o token não aparece em URL nem em logs;
  • a resposta de erro não revela o valor esperado.

Para arquiteturas sem estado de sessão no servidor, a OWASP recomenda o padrão signed double-submit cookie, ligado à sessão e protegido com HMAC. O padrão ingênuo de comparar dois valores controláveis não oferece a mesma garantia.

Use o mecanismo nativo e mantido pelo framework quando ele existir. Reimplementar geração, rotação e validação manualmente aumenta a chance de um detalhe quebrar a proteção.

Trate SameSite como uma camada adicional

Cookies de sessão devem declarar SameSite de acordo com o fluxo. Strict restringe mais, Lax permite alguns contextos de navegação e None habilita uso entre sites com Secure.

Mesmo assim, SameSite não é uma defesa completa para toda arquitetura:

  • o conceito de same-site inclui hosts irmãos sob condições definidas pelo navegador;
  • Lax preserva navegações de nível superior compatíveis;
  • clientes antigos ou incorporados podem se comportar de outra forma;
  • um código da própria aplicação pode transformar entrada controlada em requisição;
  • qualquer operação que muda estado por GET já começou com o contrato errado.

Combine o atributo com token, verificação de origem ou uma política de Fetch Metadata adequada ao risco. O guia de cookies seguros mostra como limitar sessão, host e duração.

Verifique Origin e Referer no endpoint

Para ações protegidas, o servidor pode comparar o header Origin com uma allowlist exata:

https://campanha.sitenoar.app
https://www.campanha.com.br

Compare esquema, host e porta normalizados. Não aceite uma origem porque ela contém um trecho conhecido, termina com uma string ambígua ou aparece em um parâmetro enviado pelo cliente.

Quando Origin não estiver presente em um fluxo legítimo, uma política pode verificar Referer como fallback. A decisão precisa ser explícita: ausência dos dois headers não deve virar autorização automática sem análise de compatibilidade.

Considere proxies e gateways. O serviço deve saber qual é sua origem pública confiável e não reconstruí-la a partir de headers que qualquer cliente pode forjar na borda.

Se a landing e a API usam origens diferentes, mantenha uma lista pequena. Um novo domínio próprio precisa entrar no contrato e nos testes; curingas amplos anulam a separação que a lista deveria criar.

Use Fetch Metadata para bloquear contexto incompatível

Navegadores modernos enviam headers Sec-Fetch-* que descrevem o contexto da requisição. Sec-Fetch-Site diferencia valores como same-origin, same-site, cross-site e none.

Uma política no servidor pode recusar métodos de mudança quando o valor for cross-site:

se Sec-Fetch-Site for cross-site
e o método for POST, PUT, PATCH ou DELETE
então rejeite antes de executar a ação

Esses headers têm prefixo reservado e não podem ser definidos pelo JavaScript da página. A MDN explica Fetch Metadata e como o servidor pode usá-lo para isolar recursos.

Não implemente uma regra isolada sem fallback. Alguns clientes podem não enviar esses headers, e fluxos como navegação, prefetch ou integrações incorporadas precisam de tratamento consciente. Combine a política com verificação de origem e o mecanismo de token compatível com a aplicação.

CORS não substitui proteção CSRF

CORS decide se o JavaScript de outra origem pode ler uma resposta e quais requisições o navegador autoriza após as verificações aplicáveis. Ele não impede todos os envios que um site externo consegue iniciar.

Formulários HTML podem produzir requisições consideradas simples com tipos como application/x-www-form-urlencoded, multipart/form-data ou text/plain. Se o endpoint muda estado e confia apenas no fato de a resposta não estar disponível ao atacante, a ação ainda pode acontecer.

Para uma API chamada com fetch:

  • permita origens exatas;
  • não combine credenciais com origem curinga;
  • aceite somente métodos e content types necessários;
  • exija o token ou header previsto;
  • valide tudo novamente no servidor.

O guia para validar CORS ajuda a separar preflight, credenciais e leitura da resposta.

Evite CSRF criado pelo próprio JavaScript

Uma aplicação pode incluir corretamente o token e ainda construir o destino da requisição a partir de entrada controlada:

const target = new URL(location.hash.slice(1), location.origin);
await fetch(target, {
method: "POST",
credentials: "include",
headers: { "X-CSRF-Token": csrfToken },
});

Nesse exemplo, um fragmento manipulado escolhe o destino. O código da origem confiável adiciona credenciais e token por conta própria.

Não derive endpoint, método ou identificador privilegiado diretamente de fragmento, query string, mensagem entre janelas ou dado armazenado. Use rotas fixas, valores permitidos e validação estrutural. Revise também usos de postMessage, Service Workers e SDKs que abstraem a rede.

A defesa volta ao fluxo de dados: entrada não confiável não deve controlar uma requisição autenticada.

Teste a rejeição, não apenas o sucesso

Em ambiente autorizado e com dados sintéticos, construa uma matriz:

Cenário Resultado esperado
token válido e origem permitida ação executada uma vez
token ausente rejeição sem mudança de estado
token incorreto ou expirado rejeição sem revelar detalhes
Origin externo rejeição
Sec-Fetch-Site: cross-site rejeição para método de mudança
sessão ausente resposta de autenticação apropriada
repetição da mesma requisição resultado definido e sem duplicidade
método ou content type não permitido rejeição antes do processamento

Uma chamada de diagnóstico pode simular o contexto, sem usar dados reais:

Terminal window
curl -i https://api.exemplo.com/conta/email \
-X POST \
-H 'Origin: https://origem-nao-autorizada.example' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'email=teste%40example.com'

O objetivo é observar uma rejeição e confirmar no backend que nada mudou. Não execute o teste contra produção sem autorização e um alvo exato.

No navegador, teste a URL padrão, o domínio próprio e qualquer preview autorizado. Confirme também o comportamento depois que a sessão expira e quando a pessoa usa voltar, abre outra aba ou repete o envio.

Publique o frontend e valide o backend separado

O Site no Ar publica os arquivos estáticos preparados pelo projeto. A plataforma não cria automaticamente um endpoint de formulário, uma sessão ou um token CSRF para uma API externa.

Antes da publicação:

  • remova tokens fixos da build;
  • confirme quais ações usam cookies automáticos;
  • implemente a proteção no serviço que executa a mudança;
  • configure origens exatas para URL padrão e domínios próprios necessários;
  • teste aceitação e rejeição em ambiente autorizado;
  • gere o pacote final somente depois da revisão.

Depois de publicar, abra a URL real e percorra o formulário. Verifique a requisição no navegador e a mudança no sistema de destino. Um 403 inesperado pode indicar que a origem pública ainda não entrou na allowlist; um sucesso inesperado sem token exige interromper o lançamento e corrigir o endpoint.

Quando o ajuste estiver validado, republique o mesmo Site para manter a URL e registre a versão testada do frontend e do backend.

Prompt copiável para o agente

Revise a proteção CSRF dos formulários autenticados desta landing page.
Pasta publicável: [caminho]
URL de teste: [URL]
URL padrão: [URL]
Domínios próprios: [lista]
Endpoints: [lista]
- Inventarie ações, métodos, credenciais e origens.
- Separe formulários públicos de ações autenticadas por cookie.
- Confirme que métodos seguros não alteram estado.
- Identifique token sincronizado, double-submit assinado ou proteção nativa do framework.
- Revise SameSite como defesa adicional, não como controle único.
- Valide Origin, Referer e Fetch Metadata conforme o contrato do serviço.
- Não trate CORS como substituto de CSRF.
- Busque destino de requisição controlado por URL, storage ou postMessage.
- Teste token ausente, origem externa, contexto cross-site e repetição.
- Não ataque produção nem use dados reais sem autorização.
- Entregue evidências de aceitação e rejeição, diff proposto e riscos restantes.

Checklist final

  • ações autenticadas e credenciais automáticas foram inventariadas;
  • nenhum método seguro altera estado;
  • autorização é validada além do token CSRF;
  • tokens são gerados e verificados pelo servidor;
  • não existe token fixo na build ou na URL;
  • cookies declaram SameSite compatível com o fluxo;
  • origens permitidas são exatas e justificadas;
  • Fetch Metadata tem política e fallback conscientes;
  • CORS não é a única barreira;
  • entradas do cliente não escolhem destinos privilegiados;
  • cenários de rejeição foram testados sem mudar estado;
  • URL padrão e domínios próprios foram validados;
  • frontend e backend testados ficaram registrados.

Proteção CSRF não é um campo escondido adicionado no fim. É uma verificação de servidor que conecta sessão, intenção, origem e ação. Quando o endpoint sabe rejeitar uma requisição autenticada fora do fluxo esperado, a landing pode ser publicada sem transformar conveniência do navegador em autoridade implícita.