Cross-Site Request Forgery (CSRF) e abordagens para mitigá-lo

CSRF é um dos ataques mais conhecidos, existe desde a “fundação” da Web. Ele ocorre quando uma requisição HTTP é feita entre sites na tentativa de se passar por um usuário legítimo. Quem se utiliza desse tipo de ataque normalmente foca em fazê-lo esperando que usuário alvo esteja autenticado no site onde a requisição fraudulenta será realizada, a fim de se ter mais privilégios e acessos à operações. E a razão de todo o problema está em como os navegadores lidam com os Cookies.

É mais simples entender se imaginarmos esse clássico cenário:

1) Você loga no site do seu banco usando o seu navegador preferido:

A credencial do usuário é validada, um cookie é enviado na resposta da requisição HTTP. A partir desse momento, o navegador tem salvo no disco o cookie que te mantém autenticado.

2) Você recebe um e-mail (engenharia social) que te convence a clicar em um link que abre um site arbitrário;

3) Ao entrar nesse site, no corpo dele, tem um formulário assim:

<form id="meuFormSacana" action="url-do-seu-banco/tranferir" method="POST">
    <input type="hidden" name="para" value="Kennedy">
    <input type="hidden" name="valor" value="R$100">
</form>

Esse formulário no site do atacante emula exatamente como o seu banco faz para realizar uma transferência de dinheiro.

Em seguida, em algum momento, esse site arbitrário submete o formulário usando JavaScript:

document.getElementById("meuFormSacana").submit();

É daí que vem o nome “Cross-Site Request Forgery”. Foi forjada uma requisição Cross-Site, de um site para outro.

A requisição forjada não é exatamente o problema, mas sim o fato de o seu navegador ter enviado junto com ela aquele cookie da autenticação e o site do seu banco achará que foi você quem solicitou a transferência.

O envio do cookie é feito automaticamente pelo navegador. Quando um cookie é criado, ele possui alguns atributos e um deles é o domain que fala ao navegador o domínio onde esse cookie pode ser transacionado. Uma vez que o site do atacante fez uma requisição ao domínio do seu banco através do seu navegador, o cookie disponível para esse domínio é enviado.

Obviamente esse é um exemplo hipotético e simplista, apenas com o intuito de ilustrar um caso de ataque, ademais, há muitas formas do seu banco mitigar esse tipo de ataque e certamente ele o faz.

Há de ressaltar, ainda, que esse exemplo é apenas uma das formas de ataques de CSRF. Outra muito importante e explorada é quando se tem alguma vulnerabilidade de XSS onde, por exemplo, o atacante consegue injetar um JavaScript malicioso na página do seu site a partir de um formulário de comentário que não trata os dados recebidos, muito menos escapa na hora de imprimir no HTML.

Não entraremos em todas as variantes possíveis para se explorar CSRF, focaremos nessa que é a principal. A ideia é, ao final desse artigo, oferecer soluções que resolvam de uma vez por todas esse problema em suas aplicações web.

Outro exemplo de um hipotético ataque de CSRF, esse dado pela equipe de engenharia do Dropbox:

Como se defender de CSRF?

Se o seu site usa SSL (e deveria!) você já está naturalmente protegido de algumas variantes possíveis de se explorar CSRF. Veja bem, isso é muito importante: HTTPS Everywhere. Essa é uma recomendação e um padrão que está sendo adotado pelos navegadores.

O Google Chrome na versão 68 já marca sites HTTP como sendo inseguros:

Mas, usar SSL não é a única ação necessária. Abaixo listaremos a proteção clássica que utiliza tokens e a mais nova forma de se proteger junto aos navegadores modernos, que refere-se ao atributo SameSite que pode ser especificado na criação dos cookies.

CSRF Tokens

A proteção classicamente utilizada nos formulários é a de criar um campo oculto com um token único por usuário. Esse token fica salvo na sessão do usuário no servidor e, quando o formulário é postado, o token enviado pelo formulário é comparado com o que se tem na sessão, lá no servidor. Sendo iguais, a requisição é aceita. Caso contrário, é recusada.

Você já deve ter visto formulários assim:

<form action="/endpoint" method="POST">
    <input type="hidden" name="_token" value="c2945dc5f1dd8ae0b5f9bf49daa84f29">
    <!-- ... -->
</form>

Quando o formulário é postado, é comparado:

// Se não forem iguais
if ( ! hash_equals(request('_token'), session('_token'))) {
  // Throw an exception
}

Aquele site do primeiro exemplo que fez a falsa requisição não consegue, por exemplo, requisitar o site do seu banco e extrair esse token para então utilizá-lo, isso deve à proteção de Same Origin Policy (SOP) que os navegadores implementam. E, lembre-se: o token enviado pelo formulário precisa bater com o token salvo para a sessão do usuário. Portanto, não é trivial explorar uma vulnerabilidade nessa parte. Por esse motivo, o uso de tokens sempre foi bastante efetivo para esse tipo ataque. Ainda é, dependendo do seu caso. A maioria dos frameworks web das linguagens de programação implementam essa proteção de forma bastante transparente para o desenvolvedor, ainda assim, é preciso se preocupar em quando usar o token ou não, em qual rota usar ou não etc.

E se pudéssemos resolver esse problema sem precisar implementar tokens e tudo mais? Pois bem, os navegadores modernos implementaram uma nova diretiva para cookies que resolve de uma vez por todas os problemas com CSRF. Ela se chama SameSite. Com essa diretiva, especificamos em qual circunstância o browser deve enviar o cookie em uma requisição. Ademais, essa sempre foi a raiz do problema. Lembra que comentamos lá em cima? O site malicioso só conseguiu fazer a transferência no banco pelo fato de o navegador ter enviado o cookie do usuário (autenticado) junto à requisição.

O método mais moderno: atributo SameSite para cookies

SameSite é um atributo de cookie relativamente novo. Ele foi colocado como recomendação da IETF em 2016.

Esse atributo é suportado pelo Chrome desde a versão 59. O suporte completo dos navegadores você pode conferir aqui. Ele resolve a maioria dos ataques de CSRF. É a forma mais moderna que se tem para resolver esse tipo de ataque.

Um ótimo case é do Dropbox, a equipe de engenharia deles explica aqui como aplicaram e como ele resolveu os problemas que tinham com essa vulnerabilidade.

É muito fácil configurar o atributo SameSite. Basta adicioná-lo na criação do Cookie. O Laravel (um Framework Web PHP) já suporta a configuração desse atributo. Basta especificar qual modo se deseja usar (Strict ou Lax) e então o próprio framework já trata de especificá-lo na criação do cookie.

Quando acessamos um site que necessita criar algum cookie, na resposta HTTP temos:

set-cookie: treinaweb-site=...; path=/; secure; httponly;

Quando o site especifica o atributo SameSite, ele é adicionado junto aos outros atributos:

set-cookie: treinaweb-site=...; path=/; secure; httponly; samesite=lax

Pronto. Isso é tudo. Você já estará protegido. Sem preocupação com tokens, verificações etc. Esse atributo indicará ao navegador para restringir o envio do cookie para algumas situações específicas.

O atributo SameSite pode ter dois valores possíveis: Strict e Lax, a depender de quão estrito você quer que seja. Quando o valor for Strict, o navegador não enviará o cookie em nenhuma requisição cross-site sob nenhuma hipótese.

Quando for Lax, ele apenas não enviará o cookie em requisições “inseguras” (aquelas que usam o método POST, por exemplo), mas ele enviará o cookie nas requisições cross-site “seguras”, como as que usam os métodos GET, HEAD, OPTIONS e TRACE.

Devo usar o modo Strict ou Lax no atributo SameSite?

Se você envia pro seu usuário algum link no e-mail para que ele acesse algum recurso da aplicação (uma rota que use o método GET, por exemplo) e você deseja que ele seja autenticado usando o cookie já existente no navegador, então o melhor modo pra você é o Lax. Ele permite isso.

No entanto, se você usar o modo Strict, o usuário pode estar logado, ter o cookie no navegador, mas se a requisição vier de fora do site, o navegador não vai enviar esse cookie. O modo Strict é o mais estrito possível, mas pode ser que ele tire alguma usabilidade da sua aplicação, como no caso acima, que você espera que o seu usuário seja autenticado ao clicar em um link que você enviou no e-mail pra ele. Mas, em termos gerais, ambos os modos vão te proteger do principal vetor do ataque. Não existe o “melhor” modo. Existe o que mais faz sentido para a sua aplicação e para a usabilidade dela.

Concluindo

Algumas questões e dúvidas que podem ter sido levantadas:

Devo parar de usar a estratégia de CSRF Tokens e passar a usar apenas o atributo SameSite?

Depende. Se você tem uma aplicação crítica e que possui uma base de usuários muito grande que ainda usa navegadores legados (IE10 etc), talvez deixar de usar CSRF Tokens pode não ser a melhor pedida. Nesse caso, talvez seja melhor que você use tanto os tokens quanto o atributo SameSite (já que os navegadores modernos conseguirão entendê-lo). Mas, fato é: a tendência é em poucos anos o uso desses navegadores legados não ser mais viável. Quando isso acontecer, essa questão nem entrará em pauta. SameSite será um atributo obrigatório na definição dos parâmetros de segurança de um site que usar Cookies e que lidar com informações sensíveis.

Agora, se a maioria dos seus usuários utilizam navegadores modernos e se sua aplicação não tem nada tão crítico que possa comprometê-los, você já poderia desde já adotar a estratégia de usar apenas o atributo SameSite. Vai depender da sua percepção sobre o tema.

Além de usar o atributo SameSite o que mais devo fazer?

Você deve:

  • Usar HTTPS em todo lugar;
  • Tratar os dados recebidos e escapar o que será impresso nos seus arquivos de visão (aqueles que geram HTML);
  • Cuidar da sua programação defensiva para mitigar Cross-Site Scripting (XSS);
  • Vale a pena pesquisar e estudar um pouco mais sobre “Content Security Policy”, que os navegadores modernos implementam.

Com esses pontos bem avaliados, você estará quase que integralmente protegido de ataques de CSRF.

Checklist de segurança para autenticação

Recomendo que você também leia o artigo Checklist de segurança para autenticação onde eu enumero as principais recomendações e estratégias para se desenvolver uma para autenticação segura.

Deixe seu comentário
Share

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP). @KennedyTedesco

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES