Languages

Thursday, August 2, 2018

Como invadir uma exchange

Parabéns a você que clicou no meu bait, mas fica aí que o assunto é interessante. Apesar desse título chamativo eu não invadi ninguém ok? Afinal isso seria ilegal... Mas eu descobri uma forma como eu poderia tê-lo feito com a, digamos, exchange X, sem muito trabalho, uma das exchanges Brasileiras campeãs em volume, e tudo isso estudando a plataforma por 30 minutinhos. Todas as falhas aqui descritas já foram informadas a eles e corrigidas, mas serve de aprendizado, tanto para quem faz exchange como para quem testa.

Eu só queria fazer um disclamer antes: todas as pesquisas de segurança que faço tem pura e unicamente o objetivo de fortificar a comunidade, tornando exchanges Brasileiras mais seguras e atentas, e usuários mais astutos. Este artigo tem finalidade PURAMENTE ACADÊMICA, aprenda com os erros dos outros para não os cometer; no fundo meu objetivo é formar white hats que possam ajudar esses ideais, agora, se você possui más intenções com essa informação, a porta é serventia da casa.

Se você não estiver acostumado com segurança de dados deve estar pensando nesse momento que invadir uma plataforma financeira deve ser a coisa mais complicada do mundo, afinal eles tem bastante para investir em segurança, e se mexem com dinheiro devem se preocupar bastante com isso. Espero que ao final eu tenha desmistificado isso, a coisa não é bem assim, você ficaria impressionado com o tanto de falhas bobas que acabam deixadas para trás - e essa é um bom exemplo.

Tudo começou quando eu fiz um saque de criptos na referida exchange, estava pensando aqui comigo: Por quê a API deles, dentre todas, é a única que não suporta saques? Isso é um pouco estranho e transparece medo ,implementar saques de forma segura realmente envolve riscos, são necessárias white lists de endereços, uma forma de cadastrar eles bem fechada, e por aí vai..

Apesar de estranho dei uma olhada em como o sistema requisita esses saques internamente - vai que dava pra implementar no meu robô usando esses meios. Primeiro eu olhei com o console do Chrome mesmo, era enviada a solicitação de saque para uma API que retornava um OK, até aí belezinha, seguindo fui olhar meu email:



Bom, achei esse token interessante, normalmente as exchanges usam UUID4 com um RNG criptográfico por trás, traduzindo, eles geram um numero bem grande totalmente aleatório. Esse aí parece um hexadecimal grande, mas por enquanto vamos botar um pino nisso e seguir estudando o motor de saques.

O próximo passo foi olhar a lista com meus saques solicitados para entender como ficam registrados no sistema - o meu saque ainda deve estar lá, não autorizado, afinal eu ainda não cliquei no link... Bom, vamos ver como o site requisita essa lista, e como ela vem pro navegador usando o console do Chrome de novo, guia Network...



Hohoho, papai noel chegou cedo esse ano... O retorno do backend veio com um brinde. Repare que esse aí é o mesmo token de saque que veio no email de confirmação logo acima. O que aconteceu aqui é que, talvez para fins de conveniência, talvez por esquecimento, o endpoint de listagem dos saques está vindo com todas as informações do objeto, e entre elas o link que tenho que clicar pra confirmar o saque.

Repare que aqui eu passei a chamar de objeto, pois já vi essa estrutura de informações no passado, isso aí se trata de um objeto do MongoDB, representado em Json. Note também que o tal "token" está aparecendo num campo chamado Id. Nesse momento me assustei, será mesmo que o token não é aleatório no final das contas?

Para confirmar essa suspeita joguei o "token" num conversor de ObjectId para Timestamp:



Psiu! você com olhar de paisagem! Respira que vou dar uma pausa na programação original para explicar a gravidade disso ok? 9.8 m/s2

Para entender certas coisas ajuda olhar pelos olhos do vilão, se eu fosse um hacker malvadão que acabou de pegar sua senha, ou achou sua máquina logada nessa exchange, o que me impede de sacar todo o seu saldo? O email de confirmação é para isso! Além de invadir sua conta eu precisaria invadir também o seu email, para obter o tão famigerado link que chega lá, só que não.

1- Em primeiro lugar, eu não precisaria mais invadir seu email, já que o site da exchange X me dá o token, que é a única parte que ninguém sabe do link, o trabalho é de simplesmente navegar até ele.

2- Em segundo lugar, e talvez até pior, o token não é aleatório, é determinístico, ou seja, mesmo que o site não me desse ele de mão beijada, eu poderia descobrir qual é seu token pela data e hora que solicitei o saque, mwahaha! Calma que explico a seguir:



A documentação do MongoDB diz o seguinte: "A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a 3-byte counter" - Fonte: http://www.mongodb.org/display/DOCS/Object+IDs

Bom, o timestamp eu tenho, pois fui eu quem solicitou o saque, caso não lembre ele vem na listagem de saques de toda forma, machine id é só ler em qualquer objeto, process id também, então tudo que resta é o contador sequencial do MongoDb, para resolver isso basta solicitar 2 saques em seguida, o primeiro você usa para extrair os dados, o segundo você confirma, incrementando aos poucos e testando o id, um mini-bruteforce, bobinho de ser feito.

Até o momento você leitor pode até estar decepcionado com o "bypass de email de confirmação", oras, de que serve uma falha que só quem pode explorar é quem já tem a senha do cliente? Apesar de grave, realmente é anticlimático, mas o clímax ainda está por vir...

De posse dessa falha a última peça do quebra cabeças seria uma forma de solicitar o saque no lugar do cliente, eu (o hacker malvadão), posso simplesmente fazer uma página idêntica à exchange X, e daí torcer para você (vítima) entrar nele e digitar sua senha, Laaaaaame...

Para não fazer do jeito mais chato, sobram ainda 2 possíveis metodologias:

1- Forçar o navegador da vítima a solicitar o saque.

2- Capturar o cookie contendo a sessão, e em seguida solicitar o saque.

Vamos de opção número 1, para isso eu trouxe à tona um antigo parceiro, o CSRF, dependendo de como são feitas as solicitações de saque, a nível de formulário, pode ser possível criar um segundo site "infectado" com um javascript simples, este site pode então mandar seu navegador pedir o saque, de forma invisível, basta entrar nele. Bem melhor que phishing né?

Masss nem tudo é perfeito, estudando o que é enviado no momento do saque podemos ver uma hash sendo passada, que também está presente no cookie, ou seja, a hash funciona como um token de CSRF, que a cada pedido de saque é validada novamente para garantir que esse pedido veio mesmo da exchange X. Além disso, não observamos o cabeçalho Access-Control-Allow-Origin, e de acordo com a política dos navegadores atuais, quando ele não está presente por padrão só são permitidas requisições vindas do mesmo domínio.



Com o CSRF fora da jogada, comecei a caçar um XSS Injection, o XSS (Refletido) é quando a gente consegue, através de um link, injetar javascript malicioso na página da exchange. Esse script pode fazer algumas tarefas para o atacante: enviar o cookie do cliente para algum lugar ou solicitar o saque e confirmar ele mesmo.

A idéia de injetar algo que pudesse sacar os valores e já confirmar, direto da máquina do cliente, seria excelente para um roubo em massa de Bitcoins. Os ataques, nesse caso, ficariam difíceis de detectar, afinal os saques viriam do mesmo ip do cliente; até que fosse possível encontrar o ponto de injeção (e que tinha uma injeção ocorrendo em primeiro lugar) muito dinheiro já teria sumido.

Comecei a escavar os endpoints do site, notei que o endpoint do extrato, que mostrava as operações realizadas com determinada cripto, permitia que se colocasse mais do que só o nome da moeda na url, por exemplo: /exchange/detail.php?cur=teste, e ele mostra o texto "teste" no site. Deste ponto em diante seria apenas confeccionar um script e colocar no lugar da moeda certo?



Ainda não tão simples, agora sim a parte difícil, normalmente os navegadores possuem detectores internos, para evitar que você se torne vítima de um XSS Injection, então ainda teria que bolar um script que além de efetivo pudesse enganar esse detector.

No Chrome a detecção basicamente procura por um fechamento de tag </script>, sabendo disso foi possível ultrapassar com o seguinte script: /exchange/detail.php?cur=<script>alert(1);

Isso deveria exibir uma mensagem de alerta, mas tem um problema ainda, como o site deixa o nome da cripto em maiúscula o script todo ficava em maiúsculas. No Javascript as maiúsculas influenciam no nome da função.

Esse endpoint não seria o ideal, a mensagem de alerta não surgiu pois o ALERT acabou todo em maiúsculas, foi então que decidi procurar outro endpoint melhor. Vamos atrás de um velho parceiro meu, o sistema de suporte.

Aqui foi bem fácil encontrar o Injection, no endpoint /exchange/interacao_add10.php o id do ticket provavelmente está sendo usado em algum momento para solicitar do backend as informações necessárias sobre ele, coloquei teste no lugar da hash mandei aquele CTRL+F para ver onde ele aparecia na página.



Jackpot! não só encontrei um Injection melhor, como encontrei um Injection que já está dentro de uma tag <script>!!!, isso é ótimo para ultrapassar o XSS Auditor, eu não preciso mais iniciar um <script> e finalizar com </script>, assim o bypass fica beeeem mais fácil.

Elaborei um script simples para teste, ele não faz nada malicioso, apenas uma mostra uma popup de mensagem no navegador, mas se a popup aparece então as possibilidades são infinitas...

Embuti o script na url da seguinte forma: /exchange/interacao_add10.php?h1='});alert(<texto aqui>);$.ajax({method:%27GET%27,url:%20%27json_interacoes.php



Voilá, conseguimos injetar com sucesso uma mensagem de alerta no site, qualquer um que acessar esse link também irá ver ela, mas não estamos muito interessados em espalhar mensagens de alerta por aí não é mesmo? É aqui que o hacker malvadão tem todas as ferramentas necessárias para um roubo massivo de criptomoedas.


Só um detalhe: ao clicar nesse link, aparece uma tela do Cloudfare, pedindo para eu clicar numa caixa de “eu não sou robô”. Ficou faltando preparar um script de bypass desse filtro também, mas no estado atual já está perfeitamente utilizável para um ataque, as pessoas tendem a clicar nessa caixa sem nem piscar, principalmente quando é contada uma boa história por trás.

Neste ponto a falha passou a ser crítica, acabei de descobrir que é possível criar um link que se as pessoas clicarem perdem todos os seus BTCs!

Corri para avisar a exchange e a resposta deles foi relativamente rápida, em poucos dias tudo se resolveu e o token também passou a ser aleatório de verdade. Que bom! Em troca ganhei só um muito obrigado deles, eles não gostaram muito da minha idéia de tornar as falhas públicas, mas esconder isso foge de todo o meu propósito de tornar o ambiente de criptomoedas brasileiro mais seguro.

Para o ataque em si já está quase tudo pronto, ele pode ser feito da seguinte forma: Criamos um script que solicita um saque pelo cliente e já confirma ele, com isso teremos o link bomba, podemos encurtar ele com o goo.gl ou outro serviço de diminuição de url.

Deste ponto em diante, o hacker malvadão passa a postar esse link em grupos, listas de email, sites sobre criptomoedas, comentários, uma verdadeira campanha de roubos instantâneos, todo mundo que clicar no link vai perder seu dinheiro.

Nem preciso dizer que esses últimos passos não foram tomados, evitando assim questionamentos legais, para mim basta ter encontrado a vulnerabilidade e avisado, ajudando assim na proteção dos clientes e ensinando que se você não investe em segurança, alguém investe por você, e isso nem sempre acaba com o final feliz de hoje.

1 comment: