DEV Community

Lucas Fernandes
Lucas Fernandes

Posted on

Como proteger sua aplicação frontend contra ataques CSRF

Sabe o que é e como se proteger de ataques CSRF (Cross-Site Request Forgery)?

CSRF é um tipo de ataque no qual um usuário autenticado é induzido a executar uma ação que não pretendia realizar em uma aplicação.

Imagine, por exemplo, que você está logado em um site de compras e recebe um e-mail contendo um link aparentemente inofensivo. Ao acessar esse link, uma página maliciosa tenta realizar uma ação no site em que você já está autenticado.

Se a aplicação não possuir mecanismos adequados de proteção, o navegador pode enviar automaticamente seus cookies de autenticação e a operação pode ser executada como se tivesse sido solicitada por você.

Person scared by something they saw on the computer

Existem diferentes formas de reduzir o risco desse tipo de ataque. Neste artigo, vamos abordar principalmente o uso de tokens CSRF, com foco na comunicação entre frontend e backend.

É importante destacar que o token CSRF não é o mesmo token normalmente utilizado para autenticar ou manter a sessão de um usuário. Enquanto tokens de autenticação, cookies de sessão ou access tokens identificam o usuário e controlam seu acesso à aplicação, o token CSRF tem outra função: ajudar o servidor a verificar se uma requisição que altera dados foi realmente iniciada pela aplicação legítima.

Usando tokens CSRF

Uma das abordagens mais conhecidas para prevenir ataques CSRF é o uso de tokens.

O servidor gera um valor aleatório e imprevisível, associa esse valor à sessão do usuário e o disponibiliza para a aplicação.

Quando o frontend realiza uma operação que altera o estado da aplicação, como:

  • POST
  • PUT
  • PATCH
  • DELETE

ele envia também o token CSRF.

O backend verifica esse token antes de permitir que a operação seja executada.

O fluxo pode ser representado da seguinte forma:

Servidor gera o token
        ↓
Frontend recebe o token
        ↓
Frontend envia o token na requisição
        ↓
Backend valida o token
        ↓
Requisição é aceita ou rejeitada
Enter fullscreen mode Exit fullscreen mode

É importante lembrar que requisições como GET, HEAD e OPTIONS normalmente não precisam utilizar token CSRF, pois não deveriam realizar operações que alteram dados da aplicação.

Como usar tokens CSRF em JavaScript?

Uma possível implementação pode seguir os passos abaixo.

1️⃣ Gerar o token CSRF no servidor

O token deve ser gerado utilizando uma fonte de aleatoriedade criptograficamente segura.

Por isso, não é recomendado utilizar Math.random() para gerar tokens relacionados à segurança.

Em uma aplicação Node.js, por exemplo:

import crypto from 'crypto';

function generateCSRFToken() {
  return crypto.randomBytes(32).toString('hex');
}
Enter fullscreen mode Exit fullscreen mode

Esse código gera um token aleatório de 256 bits.

O servidor também deve manter alguma forma de validar posteriormente esse token, geralmente associando-o à sessão do usuário.

2️⃣ Disponibilizar o token para o frontend

Uma aplicação renderizada pelo servidor pode disponibilizar o token diretamente no HTML.

Por exemplo:

<meta name="csrf-token" content="TOKEN_GERADO_PELO_SERVIDOR">
Enter fullscreen mode Exit fullscreen mode

No frontend, o JavaScript pode recuperá-lo:

const csrfToken = document
  .querySelector('meta[name="csrf-token"]')
  .getAttribute('content');
Enter fullscreen mode Exit fullscreen mode

Outra possibilidade, bastante comum em aplicações SPA, é utilizar um cookie específico para o token CSRF.

A arquitetura adotada depende da estratégia utilizada pela aplicação.

3️⃣ Enviar o token nas requisições

Para aplicações que utilizam AJAX ou APIs, uma prática comum é enviar o token através de um header HTTP personalizado.

Utilizando Axios:

import axios from 'axios';

const csrfToken = document
  .querySelector('meta[name="csrf-token"]')
  .getAttribute('content');

axios.post(
  '/api/cart/add',
  {
    productId: '123',
    quantity: 1
  },
  {
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

Neste exemplo:

  1. O frontend obtém o token fornecido pelo servidor.
  2. Uma requisição POST é enviada para /api/cart/add.
  3. O token é enviado através do header X-CSRF-Token.
  4. O backend valida esse valor antes de processar a operação.

Também é possível enviar o token no corpo da requisição:

import axios from 'axios';

const csrfToken = document
  .querySelector('meta[name="csrf-token"]')
  .getAttribute('content');

axios.post('/api/cart/add', {
  productId: '123',
  quantity: 1,
  csrfToken
});
Enter fullscreen mode Exit fullscreen mode

Embora as duas abordagens sejam possíveis, em aplicações AJAX ou SPA o uso de um header personalizado costuma deixar essa responsabilidade mais explícita.

Também é importante evitar colocar tokens CSRF em URLs ou query strings, como:

/api/cart/add?csrfToken=TOKEN
Enter fullscreen mode Exit fullscreen mode

Isso pode fazer com que o token apareça em locais como logs, históricos do navegador, ferramentas de analytics ou outros sistemas intermediários.

O backend precisa validar o token

A existência de um token no frontend, por si só, não oferece proteção.

O backend deve validar o token recebido antes de executar qualquer operação protegida.

Um exemplo simplificado:

app.post('/api/cart/add', (req, res) => {
  const csrfToken = req.headers['x-csrf-token'];

  if (!validateCSRFToken(req.session, csrfToken)) {
    return res.status(403).json({
      error: 'Invalid CSRF token'
    });
  }

  // Continua o processamento da requisição
});
Enter fullscreen mode Exit fullscreen mode

Ou seja, o frontend é responsável por obter e enviar o token, enquanto o backend é responsável por validá-lo.

E os cookies HttpOnly?

Aqui existe uma diferença importante.

Cookies utilizados para armazenar informações de sessão ou autenticação normalmente devem utilizar configurações como:

HttpOnly
Secure
SameSite
Enter fullscreen mode Exit fullscreen mode

Por exemplo:

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

A flag HttpOnly impede que JavaScript executado no navegador leia diretamente aquele cookie através de document.cookie.

Isso é especialmente útil para cookies de autenticação.

Porém, se o frontend precisar ler um token CSRF para colocá-lo em um header HTTP, esse token não poderá estar exclusivamente em um cookie HttpOnly.

Caso contrário, o JavaScript não conseguiria acessá-lo.

Por isso, é importante diferenciar:

Cookie de sessão
→ normalmente HttpOnly

Token CSRF utilizado pelo JavaScript
→ precisa estar disponível para o frontend
Enter fullscreen mode Exit fullscreen mode

Dependendo da arquitetura utilizada, o token pode estar presente no HTML, em uma meta tag ou em um cookie específico acessível pelo frontend.

Synchronizer Token Pattern

Uma das estratégias mais conhecidas é o Synchronizer Token Pattern.

Nesse modelo:

Sessão do usuário
       ↓
Servidor gera um token CSRF
       ↓
Token é enviado para o frontend
       ↓
Frontend envia o token novamente
       ↓
Servidor compara com o token esperado
Enter fullscreen mode Exit fullscreen mode

O token deve ser:

  • imprevisível;
  • suficientemente longo;
  • gerado de maneira criptograficamente segura;
  • associado à sessão do usuário.

Se o token recebido não corresponder ao valor esperado, o servidor rejeita a operação.

Double Submit Cookie

Outra estratégia é conhecida como Double Submit Cookie.

Nesse modelo, o servidor fornece um token em um cookie:

XSRF-TOKEN=abc123
Enter fullscreen mode Exit fullscreen mode

O frontend lê esse token e envia o mesmo valor também em um header:

X-XSRF-TOKEN: abc123
Enter fullscreen mode Exit fullscreen mode

A requisição acaba contendo as duas informações:

Cookie:
XSRF-TOKEN=abc123

Header:
X-XSRF-TOKEN: abc123
Enter fullscreen mode Exit fullscreen mode

O servidor verifica se os valores são válidos antes de aceitar a operação.

Frameworks e bibliotecas podem implementar variações dessa estratégia automaticamente.

É importante observar que existem versões mais robustas desse padrão, nas quais o token também é associado criptograficamente à sessão do usuário.

HttpOnly não protege um token exposto no HTML

É importante também entender uma limitação.

Imagine que o servidor configure:

Set-Cookie: csrfToken=abc123; HttpOnly
Enter fullscreen mode Exit fullscreen mode

mas ao mesmo tempo coloque o mesmo token no HTML:

<meta name="csrf-token" content="abc123">
Enter fullscreen mode Exit fullscreen mode

Um JavaScript malicioso não conseguirá acessar diretamente o cookie HttpOnly.

Porém, poderá acessar a meta tag:

document
  .querySelector('meta[name="csrf-token"]')
  .getAttribute('content');
Enter fullscreen mode Exit fullscreen mode

Portanto, tornar o cookie HttpOnly não protege o token caso o mesmo valor esteja disponível em algum local acessível ao JavaScript.

Essa distinção é especialmente importante quando falamos sobre ataques XSS.

CSRF e XSS são problemas diferentes

Tokens CSRF ajudam a impedir que sites externos consigam forjar determinadas operações em nome de um usuário autenticado.

Eles não devem ser considerados uma proteção contra Cross-Site Scripting (XSS).

Se um atacante conseguir executar JavaScript dentro da própria origem da aplicação, ele pode, dependendo do cenário:

  • acessar informações disponíveis no DOM;
  • obter tokens acessíveis ao JavaScript;
  • realizar requisições em nome do usuário;
  • manipular a aplicação diretamente.

Por isso, CSRF e XSS precisam ser tratados como problemas de segurança distintos.

Outras medidas importantes contra CSRF

Além dos tokens CSRF, existem outras proteções que podem ser utilizadas em conjunto.

SameSite nos cookies

A propriedade SameSite ajuda a controlar quando cookies podem ser enviados em requisições iniciadas a partir de outros sites.

Por exemplo:

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

Existem três valores principais:

SameSite=Strict
SameSite=Lax
SameSite=None
Enter fullscreen mode Exit fullscreen mode

Strict oferece uma política mais restritiva.

Lax permite alguns fluxos de navegação entre sites, mas reduz vários cenários tradicionais de CSRF.

Já SameSite=None permite o envio do cookie em contexto cross-site e exige também Secure.

A configuração correta depende do funcionamento da aplicação.

SameSite deve ser entendido como uma camada adicional de proteção e não necessariamente como substituto de todas as demais medidas.

Validar Origin e Referer

O backend também pode verificar headers como:

Origin
Referer
Enter fullscreen mode Exit fullscreen mode

Por exemplo:

Origin: https://meusite.com
Enter fullscreen mode Exit fullscreen mode

O servidor pode verificar se a requisição está vindo de uma origem esperada antes de permitir determinadas operações.

Fetch Metadata

Navegadores modernos também enviam headers conhecidos como Fetch Metadata, como:

Sec-Fetch-Site
Sec-Fetch-Mode
Sec-Fetch-Dest
Enter fullscreen mode Exit fullscreen mode

Por exemplo:

Sec-Fetch-Site: same-origin
Enter fullscreen mode Exit fullscreen mode

Esses headers podem ajudar o servidor a identificar requisições iniciadas a partir de outros sites.

Eles podem ser utilizados como uma camada adicional de proteção.

Não alterar estado através de GET

Uma regra importante é evitar operações como:

GET /delete-user?id=123
Enter fullscreen mode Exit fullscreen mode

ou:

GET /purchase?id=456
Enter fullscreen mode Exit fullscreen mode

Requisições GET devem ser utilizadas para leitura de recursos e não para operações que alterem dados.

Operações de alteração devem utilizar métodos apropriados, como:

POST
PUT
PATCH
DELETE
Enter fullscreen mode Exit fullscreen mode

com as respectivas proteções de segurança.

Reautenticação para operações críticas

Para operações muito sensíveis, a aplicação também pode exigir uma confirmação adicional.

Por exemplo:

Alterar senha
Alterar e-mail
Cadastrar uma nova conta bancária
Realizar uma transferência financeira
Excluir permanentemente uma conta
Enter fullscreen mode Exit fullscreen mode

Nesses casos, solicitar novamente a senha, MFA ou outra confirmação pode adicionar uma camada extra de segurança.

Isso não substitui as proteções contra CSRF, mas pode reduzir o impacto de diferentes tipos de ataques.

Same-Origin Policy não elimina CSRF

Outro ponto importante é entender a relação entre CSRF e Same-Origin Policy.

A Same-Origin Policy é uma proteção implementada pelos navegadores que limita como páginas de uma origem conseguem acessar dados de outra origem.

Por exemplo:

https://site-malicioso.com
Enter fullscreen mode Exit fullscreen mode

normalmente não consegue ler livremente informações retornadas por:

https://meubanco.com
Enter fullscreen mode Exit fullscreen mode

Porém, isso não significa necessariamente que o navegador não possa enviar uma requisição para meubanco.com.

Essa diferença é justamente uma das razões pelas quais ataques CSRF são possíveis.

De forma simplificada:

Same-Origin Policy
↓
limita principalmente a leitura de recursos entre origens

CSRF
↓
explora o envio de requisições usando a sessão já autenticada da vítima
Enter fullscreen mode Exit fullscreen mode

Por isso, a Same-Origin Policy não deve ser considerada uma proteção suficiente contra CSRF.

Conclusão

Ataques CSRF exploram principalmente o fato de que navegadores podem enviar automaticamente credenciais, como cookies de sessão, em determinadas requisições.

Uma estratégia de proteção pode combinar diferentes mecanismos:

Tokens CSRF
+
SameSite
+
validação de Origin/Referer
+
Fetch Metadata
+
cookies Secure e HttpOnly para autenticação
+
operações que alteram estado usando métodos HTTP adequados
Enter fullscreen mode Exit fullscreen mode

O ponto principal é lembrar que a proteção contra CSRF não deve depender apenas do frontend.

O frontend pode obter e enviar o token, mas é o backend que precisa verificar se a requisição é legítima antes de executar a operação.

Segurança funciona melhor quando diferentes mecanismos são utilizados em conjunto, reduzindo a dependência de uma única camada de proteção.

Glossário

Top comments (1)

Collapse
 
devsupport profile image
Dev Support •

Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support

‍ ​‌