CSRF e sessões seguras: a falha que faz o navegador da vítima agir contra ela
Como o Cross-Site Request Forgery abusa da sessão automática do navegador e como cookies SameSite, tokens anti-CSRF e sessões bem desenhadas fecham o vetor.
Neste artigo
Uma pessoa está logada no internet banking numa aba. Em outra aba, ela abre um site qualquer que recebeu por e-mail — um cupom, um meme, nada de especial. Escondida naquela página há uma imagem invisível cujo endereço aponta para https://banco.com/transferir?para=atacante&valor=5000, ou um formulário que se envia sozinho por JavaScript para o mesmo destino. O navegador, ao carregar aquele recurso, faz o que sempre faz: anexa automaticamente os cookies do banco à requisição, porque ela é para o domínio do banco. O servidor do banco recebe uma requisição perfeitamente autenticada — os cookies de sessão válidos estão lá — e executa a transferência. A vítima nunca autorizou nada; ela só abriu uma página errada com a sessão do banco ativa. Isso é Cross-Site Request Forgery, ou CSRF.
CSRF é uma falha traiçoeira porque não explora um bug no seu código no sentido usual — ele explora um comportamento correto e fundamental do navegador: enviar cookies automaticamente para o domínio a que pertencem. É essa automação, combinada com a ausência de uma verificação de intenção, que abre a porta. Entender exatamente o mecanismo é o que torna a defesa óbvia.
O mecanismo: autenticação ambiente#
A raiz do CSRF está no que se chama de autenticação ambiente (ambient authority). Quando a autenticação de uma aplicação depende apenas de algo que o navegador envia sozinho — tipicamente o cookie de sessão — qualquer requisição que chegue ao servidor com aquele cookie parece legítima, independentemente de quem originou a requisição. O servidor não distingue "o usuário clicou no meu botão de transferência" de "outro site fez o navegador do usuário disparar a mesma requisição". Ambas chegam com o mesmo cookie válido.
O ataque não precisa ler a resposta — e de fato a política de mesma origem do navegador geralmente impede o site atacante de ler o que o banco respondeu. Mas para uma ação de efeito colateral, como transferir dinheiro, apagar uma conta ou trocar um e-mail cadastrado, ler a resposta é irrelevante: o dano já foi feito no envio. Por isso CSRF mira operações de escrita, não de leitura.
Por que GET nunca deve mudar estado#
Uma das lições estruturais que o CSRF ensina é por que requisições GET jamais devem causar efeito colateral. Um GET é disparável de inúmeras formas silenciosas — uma tag de imagem, um link pré-carregado, um redirecionamento — e o navegador o executa sem cerimônia. Se GET /transferir?valor=5000 efetua a transferência, basta uma imagem escondida para explorá-lo. Reservar GET para leitura idempotente e exigir POST, PUT, PATCH ou DELETE para qualquer mudança de estado não é só bom design REST — é uma precondição de segurança, porque as defesas anti-CSRF se apoiam em métodos que o navegador trata com mais cautela e em corpos de requisição que sites de terceiros têm dificuldade de forjar corretamente.
Defesa 1: cookies SameSite#
A defesa mais moderna e de maior alcance é o atributo SameSite no cookie de sessão. Ele instrui o navegador sobre quando enviar o cookie em requisições que se originam de outro site. Com SameSite=Strict, o cookie nunca é enviado numa requisição iniciada por outra origem — o que mata o CSRF, mas também impede que o usuário continue logado ao chegar por um link externo. Com SameSite=Lax, hoje o padrão na maioria dos navegadores, o cookie é enviado em navegações de nível superior por GET (clicar num link funciona), mas não em requisições cross-site de método que muda estado nem em subrequisições como imagens e formulários automáticos — o que barra o vetor clássico do CSRF mantendo a usabilidade. SameSite=None desliga a proteção e só deve ser usado, com Secure, quando a integração cross-site é genuinamente necessária. Definir SameSite=Lax no mínimo é hoje um piso, mas não deve ser a única linha de defesa, porque nem todo cenário é coberto e navegadores antigos variam.
Defesa 2: tokens anti-CSRF#
A defesa clássica, ainda essencial, é o token anti-CSRF, também chamado de token sincronizador. O servidor gera um valor imprevisível, único por sessão (ou por requisição), e o entrega à página legítima — tipicamente num campo oculto de formulário ou num cabeçalho lido por JavaScript. Toda requisição que muda estado precisa devolver esse token, e o servidor rejeita qualquer uma que não traga o valor correto. O segredo funciona porque um site atacante, submetido à política de mesma origem, não consegue ler o token da sua página — ele pode fazer o navegador disparar a requisição, mas não sabe qual valor incluir, e a requisição forjada é recusada. O token precisa ser gerado por CSPRNG, ser imprevisível, e ser validado no servidor a cada operação sensível. Uma variante sem estado é o padrão double-submit, em que o token vai tanto num cookie quanto no corpo/cabeçalho e o servidor compara os dois — útil quando não se quer guardar o token no servidor, com cuidados adicionais na configuração dos cookies.
Defesa 3: verificar a origem#
Como camada adicional, o servidor pode inspecionar os cabeçalhos Origin e Referer da requisição e rejeitar operações de escrita cuja origem não seja a esperada. É uma verificação barata e eficaz para muitos casos, embora não deva ser a única defesa, porque há situações em que esses cabeçalhos podem estar ausentes por razões de privacidade. Combinada com SameSite e tokens, ela compõe a defesa em profundidade que caracteriza uma boa postura contra CSRF: não uma barreira, mas várias, de modo que a falha de uma não abre o portão.
Sessões seguras: o outro lado da moeda#
CSRF só é perigoso porque existe uma sessão autenticada para sequestrar, e por isso a higiene de sessão é inseparável do tema. O cookie de sessão precisa carregar três atributos como piso: HttpOnly, para que o JavaScript não consiga lê-lo e um XSS não roube a sessão; Secure, para que ele só trafegue sob HTTPS e não vaze em conexão sem criptografia; e SameSite, pelo motivo já discutido. Faltar qualquer um desses é deixar uma porta encostada.
O identificador de sessão em si precisa ser gerado por um gerador criptograficamente seguro (CSPRNG), com entropia suficiente para ser impraticável de adivinhar — nunca por Math.random(), nunca por um contador sequencial, nunca derivado de dados previsíveis do usuário. Um identificador de sessão adivinhável permite sequestro sem nem precisar de CSRF ou XSS: o atacante simplesmente chuta sessões válidas.
Ciclo de vida da sessão#
Uma sessão bem desenhada tem um ciclo de vida deliberado. No login, o identificador de sessão deve ser renovado — gerar um novo id após a autenticação previne a fixação de sessão, um ataque em que o adversário planta um id conhecido antes do login e o herda depois que a vítima autentica. A sessão precisa ter expiração — tanto por inatividade quanto uma expiração absoluta — para limitar a janela em que um cookie roubado é útil. E o logout precisa ser de verdade: invalidar a sessão no servidor, não apenas apagar o cookie no cliente. Um logout que só remove o cookie mas deixa a sessão viva no servidor é logout de fachada — quem tiver capturado o cookie continua com acesso. Operações especialmente sensíveis, como trocar senha ou e-mail, merecem reautenticação ou verificação adicional, justamente para que uma sessão sequestrada não baste para tomar a conta em definitivo.
APIs, tokens e o CSRF que muda de forma#
Muita gente conclui, ao aprender que APIs autenticadas por token no cabeçalho Authorization não sofrem CSRF, que o problema desapareceu na era das SPAs. A conclusão é parcialmente correta e perigosamente incompleta. Um token que a aplicação anexa deliberadamente a cada requisição, lendo-o de um lugar que só o seu código acessa, não é enviado automaticamente pelo navegador — e sem envio automático não há CSRF, porque o site atacante não tem como incluir aquele cabeçalho numa requisição forjada. O problema é que, para evitar CSRF assim, você precisa guardar o token de forma que o JavaScript o leia, e o local mais comum para isso, o localStorage, é exatamente o que um XSS saqueia com facilidade. Você troca um vetor por outro.
É por isso que a arquitetura mais defensável para aplicações web continua sendo a sessão em cookie HttpOnly — imune a roubo por XSS — combinada com as defesas anti-CSRF que o cookie, por ser de envio automático, torna necessárias. Não existe escolha sem contrapartida: cookie automático precisa de proteção anti-CSRF; token manual em armazenamento acessível precisa de proteção anti-XSS impecável. Entender esse trade-off, em vez de repetir "APIs com token não têm CSRF" como se fosse um passe livre, é o que permite escolher conscientemente e fechar o vetor que a sua escolha deixou aberto. Padrões modernos que guardam o token de acesso em memória e o de renovação num cookie HttpOnly tentam colher o melhor dos dois lados, à custa de mais complexidade.
Fixação de sessão e a renovação no login#
Vale abrir com mais cuidado o ataque de fixação de sessão, porque ele é sutil e frequentemente esquecido. Em vez de roubar uma sessão existente, o atacante fornece uma: ele obtém um identificador de sessão válido do próprio site e induz a vítima a autenticar usando aquele id — por um link com o id embutido, por exemplo, em sistemas que aceitam o identificador vindo da URL. Se a aplicação, ao autenticar a vítima, mantém o mesmo identificador que já estava em uso, o atacante — que conhece o id desde o início — passa a compartilhar a sessão agora autenticada. A defesa é mecânica e barata: gerar um identificador de sessão novo no momento exato do login, invalidando qualquer id anterior. Assim, mesmo que o atacante tenha plantado um id, ele morre no instante da autenticação, e o novo id nasce conhecido apenas pelo servidor e pela vítima.
Onde tudo se conecta#
CSRF, XSS e gestão de sessão formam um triângulo que se reforça mutuamente. Um XSS derrota muitas defesas de CSRF, porque um script rodando na sua origem pode ler o token anti-CSRF e forjar requisições legítimas — por isso HttpOnly no cookie e escape de saída rigoroso importam para a defesa de CSRF também. Uma sessão sem SameSite reabre o CSRF que o token deveria ter fechado. Um logout de fachada mantém viva a sessão que todas as outras defesas tentavam proteger. A conclusão prática é que segurança de aplicação não se faz por controles isolados, e sim por um conjunto coerente: cookies com os três atributos, identificadores por CSPRNG, renovação no login, expiração e logout reais, métodos HTTP com semântica respeitada, e defesa anti-CSRF em camadas. Nenhum desses itens é caro ou exótico; o que custa é lembrar de todos, sempre, e é para isso que existem checklists de definição de pronto e revisões de segurança que perguntam, a cada feature que mexe em estado autenticado, se o vetor foi fechado.
