Entendendo os ataques CSRF
Recentemente fui pesquisar sobre segurança na web enquanto escrevia Understanding Asynchronous JavaScript - queria ter certeza de que minhas recomendações eram seguras e não estou prestando um mau serviço a nenhum de meus alunos com minhas recomendações.
Infelizmente, os artigos na área de segurança eram bastante difíceis de entender. Havia muitas palavras que provocavam muito medo, incerteza e dúvida nos artigos. Eu fico emocionalmente em pânico quando leio esses artigos - e me preocupo que posso acabar fazendo algo errado - mesmo que a intenção destes artigos fosse boa!
Muitos artigos também não revelam todos os detalhes do CSRF, como criar um Ataque CSRF e como evitar um Ataque CSRF, o que me deixa confuso sobre o que aprendi. Eu acabo tendo que descobrir as coisas sozinho.
Quero facilitar a compreensão do CSRF, por isso, fiz um esforço ao escrever um artigo com informações completas (e passo a passo) sobre os Ataques de CSRF. Espero que este artigo lhe dê a clareza e confiança necessárias para construir apps web seguros.
Dois tipos de Ataques CSRF
Há dois tipos de Ataques CSRF:
- Normal CSRF
- Login CSRF
Veremos primeiro o Normal CSRF, em seguida o Login CSRF.
O que é um Ataque CSRF
Um Ataque CSRF é aquele que engana uma vítima e a faz enviar uma solicitação maliciosa - uma solicitação que ela não pretendia fazer - a um website onde ela está autenticada (logada).
A solicitação deve ter origem em outro website, que dá o nome "Cross-Site". Esta solicitação também imita um usuário autenticado, o que lhe dá o nome "Request Forgery".
Os Ataques CSRF são cegos - o que significa que o atacante não vê o que acontece depois que a vítima submete a solicitação. Portanto, os ataques CSRF frequentemente têm como alvo uma mudança de estado no servidor.
O que é uma mudança de estado? Basicamente, qualquer coisa que modifique o banco de dados é uma mudança de estado. Exemplos de mudanças de estado incluem:
- Mudança de nome de usuário e senha
- Envio de dinheiro para uma conta
- Envio de mensagens falsas a partir da conta do usuário
- Compartilhar imagens ou vídeos inapropriados da conta do usuário
Os ataques do CSRF aproveitam o fato de que os navegadores enviam cookies automaticamente para o servidor em cada solicitação. Sem qualquer proteção contra CSRF, o servidor pode assumir que uma solicitação é válida quando um cookie de autenticação está presente.
Os cookies de autenticação podem ser qualquer coisa, desde que o servidor os utilize para verificar se um usuário é válido. Pode ser um token de acesso. Também pode ser um ID de sessão. Depende de como o servidor lida com a autenticação.
Pré-requisitos para que o Ataque CSRF funcione
Há quatro pré-requisitos necessários para que um Ataque CSRF seja bem sucedido.
- Uma solicitação de qualquer método é enviada para o servidor.
- O usuário deve estar autenticado.
- O servidor deve armazenar informações de autenticação em cookies.
- O servidor não implementa técnicas de prevenção de CSRF (que serão discutidas abaixo).
Como funcionam os Ataques CSRF
Antes que um atacante possa lançar um Ataque CSRF, ele precisa encontrar uma solicitação consistente que possa atingir. Ele deve saber o que a solicitação faz. Esta pode ser qualquer solicitação - GET, POST, PUT, ou DELETE. Qualquer coisa serve.
Uma vez selecionada a solicitação a ser o alvo, ele deve gerar uma solicitação falsa para enganar o usuário.
Finalmente, ele deve induzir o usuário a enviar a solicitação. Na maioria das vezes, isto significa:
Encontrar uma forma de enviar a solicitação automaticamente sem que o usuário saiba. As abordagens mais comuns são através de tags de imagem e o envio automático de um formulário JavaScript.
falsificar um link (ou botão), o que induz o usuário a clicar nele. (também conhecido como Engenharia Social).
Ataques através de uma solicitação GET
Os Ataques CSRF com uma solicitação GET só funcionam se o servidor permitir que um usuário mude de estado com solicitações GET. Você não precisa se preocupar com este tipo de Ataque CSRF se suas solicitações GET forem somente de leitura.
Mas digamos que temos um servidor que não segue as melhores práticas de programação e permite mudanças de estado através de uma solicitação GET. Se eles fizerem isso, eles estarão em apuros - enormes problemas.
Por exemplo, digamos que há um banco que permite que você transfira dinheiro com o seguinte endpoint. Você só tem que entrar com account
e amount
na solicitação GET para enviar dinheiro a uma pessoa.
https://bank.com/transfer?account=Mary&amount=100
O atacante pode gerar um link que envia o dinheiro para sua conta.
# Envia 9999 para a conta do atacante
https://bank.com/transfer?account=Attacker&amount=9999
Neste ponto, o atacante pode encontrar uma maneira de acionar o link automaticamente sem que o usuário saiba.
Uma maneira é incluir o link em uma imagem 0x0 em uma página web ou em um e-mail. Se o usuário visitar esta página web ou e-mail, a solicitação GET é acionada automaticamente, uma vez que os navegadores e e-mails são configurados para buscar imagens automaticamente.
(Agora entendo porque os provedores de e-mail desabilitam o carregamento de imagens como uma precaução de segurança).
<!-- O download desta imagem desencadeia o ataque de solicitação GET -->
<img
src="https://bank.com/transfer?account=Attacker&amount=9999"
width="0"
height="0"
border="0"
/>
Outra maneira é deturpar o que um link faz. Isto funciona porque as pessoas não verificam os links antes de clicar neles. Se a pessoa clicar no link, ela irá enviar a solicitação GET para o atacante sem saber.
<!-- link falso que desencadeia o ataque de solicitação GET -->
<a href="https://bank.com/transfer?account=Attacker&amount=9999"
>View my Pictures</a
>
Se o usuário estiver autenticado, o servidor receberá um cookie de autenticação que o faz acreditar que a solicitação é válida. Se o servidor não utilizar nenhum mecanismo de proteção CSRF, o dinheiro será enviado para o atacante.
Exemplos de Ataques CSRF com solicitações GET
uTorrent sofreu um Ataque CSRF em 2008, que permitiu mudanças de estado com solicitações GET.
O Youtube costumava ter uma vulnerabilidade de segurança em 2008 que permitia ao atacante realizar quase todas as ações possíveis para um usuário, inclusive enviar mensagens, adicionar a uma lista de amigos, etc.
Se você clicar nos links acima. Você poderá encontrar exemplos de solicitações GET reais que criam um Ataque CSRF. (Não se preocupe, nenhum link é perigoso aqui 😜).
Ataques CSRF com solicitação POST
Os Ataques CSRF com solicitação POST seguem o mesmo padrão - mas não podem ser enviados através de links ou tags de imagem. Eles precisam ser enviados através de um formulário ou através do JavaScript.
Vamos assumir que temos o mesmo endpoint vulnerável e que o atacante simplesmente precisa entrar com a informação de account
e amount
para acionar a solicitação..
POST https://bank.com/transfer?account=Attacker&amount=9999
O atacante pode criar um formulário e ocultar os valores de account
e amount
do usuário. As pessoas que clicarem neste formulário falso enviaram a solicitação POST sem saber.
<!-- Formulário disfarçado como um botão! -->
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="acct" value="Attacker" />
<input type="hidden" name="amount" value="9999" />
<button>View my pictures</button>
</form>
Este formulário também pode ser executado automaticamente com o JavaScript sem que as pessoas saibam - usuários reais nem precisam clicar no botão, e já estão com problemas.
<form>...</form>
<script>
const form = document.querySelector('form')
form.submit()
</script>
Ataques POST CSRF são assustadores, mas há maneiras de evitá-los. Falaremos sobre as técnicas na seção de prevenção abaixo.
Ataques CSRF com solicitações PUT e DELETE
Os Ataques CSRF não podem ser executados com solicitações PUT
e DELETE
porque as tecnologias que utilizamos não permitem que isso ocorra.
Sim. Você leu isso certo.
Os Ataques CSRF não podem ser executados através de formulários HTML porque os formulários não suportam solicitações PUT
e DELETE
. Ele só suporta GET
e POST
. Se você usar qualquer outro método (exceto GET
e POST
), os navegadores os converterão automaticamente em uma solicitação GET.
<!-- O formulário não envia uma solicitação PUT porque o HTML não suporta o método PUT. Isto se transformará em uma solicitação GET. -->
<form action="https://bank.com/transfer" method="PUT"></form>
Portanto, não é possível executar um Ataque CSRF através de um HTML.
Agora, aqui está algo engraçado: como as pessoas enviam solicitações PUT
e w
através de um formulário se o HTML não o permite? Após algumas pesquisas, descobri que a maioria dos frameworks permitem que você envie uma solicitação POST
com um parâmetro _method
.
<!-- Como a maioria das estruturas lidam com as solicitações PUT -->
<form method="post" ...>
<input type="hidden" name="_method" value="put" />
</form>
Você pode executar um Ataque CSRF com solicitações PUT
via JavaScript, mas o mecanismo de prevenção padrão em navegadores e servidores hoje em dia torna realmente difícil que estes ataques aconteçam - você tem que deliberadamente desativar as proteções para que isso aconteça.
Eis o porquê.
Para executar um Ataque CSRF com solicitações PUT
, você precisa enviar uma solicitação de busca com o método put
. Você também precisa incluir a opção credentials
.
const form = document.querySelector('form')
// Envia a solicitação automaticamente
form.submit()
// Intercepta o envio do formulário e utiliza o Fetch para enviar um pedido AJAX em seu lugar.
form.addEventListener('submit', event => {
event.preventDefault()
fetch(/*...*/, {
method: 'put'
credentiials: 'include' // Inclui cookies na solicitação
})
.then(/*...*/)
.catch(/*...*/)
})
Isso não funcionaria devido a três razões.
Primeiro, esta solicitação NÃO será executada pelos navegadores automaticamente por causa do CORS. A menos - é claro - que o servidor crie uma vulnerabilidade ao permitir solicitações de qualquer pessoa com o seguinte cabeçalho:
Access-Control-Allow-Origin: *
Segundo, mesmo que você permita que todas as origens acessem o seu servidor, você ainda precisa de uma opção Access-Control-Allow-Credentials
para que os navegadores possam enviar cookies para o servidor.
_Access-Control-Allow-Credentials: true_
Terceiro, mesmo se você permitir o envio de cookies para o servidor, os navegadores só enviarão cookies que tenham o sameSite
definido para none
. (Estes também são chamados third - party cookies ).
Se você não tem idéia do que estou falando a respeito do terceiro ponto, você está seguro - você realmente tem que ser um desenvolvedor malicioso que quer estragar seu servidor se você enviar cookies de autenticação como cookies third-party.
Esta seção é enorme. Eu criei mais alguns artigos para ajudar você a entender exatamente o que está acontecendo - e por que é tão difícil expor-se a um Ataque PUT
CSRF:
Resumindo - você só tem que se preocupar com os Ataques POST
CSRF a menos que realmente tenha estragado seu servidor
Métodos de prevenção CSRF
Os métodos de prevenção CSRF mais comuns atualmente são:
- Padrão Double Submit Cookie
- Método Cookie to header
Os dois métodos seguem a mesma fórmula.
Quando o usuário visita seu site, seu servidor deve criar um token CSRF e colocá-los nos cookies do navegador. Nomes comuns para este token são:
- _CSRF-TOKEN
- X-SRF-TOKEN
- X-XSRF-TOKEN
- X-CSRF-TOKEN_
Use qualquer um dos nomes de token que você quiser. Todos eles funcionam.
O importante é que o Token CSRF deve ser uma string gerada aleatoriamente e criptograficamente forte. Se você usar Node, você pode gerar a string com crypto
.
import crypto from 'crypto'
function csrfToken (req, res, next) {
return crypto.randomBytes(32).toString('base64')
}
Se você utiliza Express, você pode colocar este token CSRF em seus cookies desta forma. Ao fazer isso, recomendo usar sameSite
também. (Falaremos sobre o sameSite
daqui a pouco).
import cookieParser from 'cookie-parser'
// Use isso para ler os cookies
app.use(cookieParser())
// Definição do Token CSRF para todos os endpoints
app.use(*, (req, res) => {
const { CSRF_TOKEN } = req.cookies
// Define o token se o usuário visitar esta página pela primeira vez nesta sessão
if (!CSRF_TOKEN) {
res.cookie('CSRF_TOKEN', csrfToken(), { sameSite: 'strict' })
}
})
Como você usa o Token CSRF, muda dependendo de como você subscreve o padrão de envio, com Double cookie ou o método Cookie to header (ou ambos).
Padrão Double Submit Cookies
O nome deste padrão é um pouco enganoso - porque parece significar o envio de um cookie duas vezes com "Double Submit Cookie".
O que isto realmente significa é:
Você envia o Token CSRF em um cookie
Você torna o <form>
com um Token CSRF - que seria incluído na apresentação do formulário.
(Daí o duplo envio).
Se você usar Express, você pode passar o Token CSRF para o HTML desta forma:
app.get('/some-url', (req, res) => {
const { CSRF_TOKEN } = req.cookies
//Renderização com Nunjucks.
// Substitua o Nunjucks por qualquer outro modelo de engine que você use
res.render('page.nunjucks', {
CSRF_TOKEN: CSRF_TOKEN
})
})
Então você pode usar CSRF_TOKEN
desta forma:
<form>
<input type="hidden" name="csrf" value="{{CSRF_TOKEN}}" />
<!-- ... -->
</form>
O servidor pode então verificar a validade da sessão, comparando dois Tokens CSRF. Se corresponderem, significa que a solicitação não é falsificada - porque não há maneira de um atacante adivinhar o valor do token CSRF em outro site.
// Verifica a validade do Token CSRF
app.post('/login', (req, res) => {
const { CSRF_TOKEN } = req.cookies
const { csrf } = req.body
// Abortar a solicitação
// Você também pode lançar um erro se desejar
if (CSRF_TOKEN !== csrf) return
// ...
})
Método Cookie to Header
O método cookie to header é semelhante - exceto que é executado com o JavaScript. Neste caso, o Token CSRF deve ser incluído tanto no cookie quanto no cabeçalho da solicitação.
Neste caso, é necessário:
Definir credentials
para include
ou same-origin
para incluir cookies
Pegue o token CSRF do document.cookies
e adicione-o como um cabeçalho de solicitação.
Aqui está um exemplo de solicitação:
// Obtém o valor de um cookie nomeado
function getCookie () {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
if (match) return match[2]
}
// Envia o pedido
fetch('/login', (req, res) => {
credentials: 'include',
headers: {
'CSRF_TOKEN': getCookie('CSRF_TOKEN')
}
})
O servidor pode verificar a validade do Token CSRF desta forma:
// Verifica a validade do Token CSRF
app.post('/login', (req, res) => {
const { CSRF_TOKEN } = req.cookies
const { CSRF_TOKEN: csrf } = req.headers
// Abortar o pedido
// Você também pode lançar um erro se desejar
if (CSRF_TOKEN !== csrf) return
// ...
})
Facilite tudo isso com uma biblioteca
Eu te mostrei como criar e testar manualmente os Tokens CSRF porque eu queria dar a você uma compreensão do processo.
Este processo já foi resolvido muitas vezes, portanto não devemos fazê-lo manualmente (a menos que você esteja aprendendo, como o que eu fiz aqui).
Se você usar Express, eu recomendo o uso da biblioteca csurf, já que ela é mais robusta e flexível em comparação com o que eu mostrei no exemplo acima.
Atributo SameSite Cookie
A configuração do sameSite
para strict
no exemplo acima garante que o cookie CSRF Token só seja enviado ao servidor se a solicitação for originada do mesmo website. Isto assegura que o CSRF Token nunca será revelado para páginas externas.
Você pode - opcionalmente, mas recomendado - definir o atributo do sameSite
para strict
, assim como você define o cookie de autenticação. Isto assegura que nenhum Ataque CSRF possa ser conduzido, uma vez que o cookie de autenticação não será mais incluído nas solicitações entre sites.
Você precisa da proteção do token CSRF se você usou o sameSite
para strict como seu cookie de autenticação?
Eu diria que não na maioria dos casos - porque o sameSite
já protege o servidor de solicitações entre sites. Mas ainda precisamos do token CSRF para proteger contra um tipo particular de CSRF: Login CSRF.
Você pode ler mais sobre os cookies do sameSite neste artigo.
Login CSRF
Um Login CSRF é completamente diferente de um Ataque CSRF normal em termos de intenção.
Em um Login CSRF, o atacante engana um usuário para entrar no sistema com as credenciais do atacante. Quando o ataque é bem sucedido, o usuário continuará usando a conta do atacante se não estiver atento.
<form action="http://target/login" method="post">
<input name="user" value="Attacker" />
<input name="pass" type="password" value="AttackerPassword" />
<button>Submit</button>
</form>
Eles também podem acionar o formulário automaticamente com JavaScript.
const form = document.querySelector('form')
// Envia a solicitação automaticamente
form.submit()
Se o usuário não se der conta de que logou na conta do agressor, pode adicionar dados pessoais - como informações de cartão de crédito ou histórico de busca - à conta. Os atacantes podem então voltar a entrar em suas contas para visualizar estes dados.
O Google era vulnerável contra ataques de Login CSRF no passado.
Podemos impedir o Login CSRF com o padrão Double Submit Cookie mencionado acima - os atacantes não poderão adivinhar o Token CSRF, o que significa que não poderão lançar um Ataque de Login CSRF.
Encerrando
CSRF significa Cross Site Request Forgery (Solicitação de Falsificação Cruzada). Há dois tipos de Ataques CSRF:
Normal CSRF
Login CSRF
No Normal CSRF, o atacante tem como objetivo criar uma mudança de estado através de uma solicitação.
No CSRF Login, o atacante tem o objetivo de enganar o usuário para entrar na conta do atacante e, espera se beneficiar das ações do usuário se ele não estiver ciente.
Você pode evitar ambos os tipos de Ataques CSRF com o padrão Double Submit Cookie e o método Cookie to header. A configuração sameSite
para strict
impede o Normal CSRF, mas não o Login CSRF.
É isso aí!
Obrigado por ler. Este artigo foi originalmente publicado em meu blog. Assine minha newsletter se você quiser mais artigos para ajudá-lo a se tornar um melhor desenvolvedor de front-end.
Artigo escrito por Zell Liew e traduzido para o Português por Rafael Ojeda
Você pode encontrar o artigo original em inglês aqui
Top comments (0)