Por Felipe Azevedo
Este blogpost dá continuidade à saga de artigos sobre problemas em aplicações Web e/ou mobile. Nosso objetivo é o de reforçar que sempre é possível aplicar diversas camadas de segurança para proteger a sua empresa/negócio. Sendo assim daremos início a uma série de blogposts onde iremos discorrer sobre alguns dos principais problemas das más implementações — no ponto de vista de segurança — das regras de negócio; validação de dados; múltiplos estágios; procedimento de cadastros; fingerprints e reconhecimento facial; subversões com chamadas de activities em aplicações Android; além dos problemas relacionados às perguntas e respostas secretas.
Mais especificamente neste texto, apresentaremos alguns problemas relacionados à autenticação multifator. Não necessariamente problemas no MFA (Multi-Factor Authentication) em si, mas sim os problemas de segurança encontrados nas implementações utilizadas em aplicações atuais.
Para quem não conhece ou nunca ouviu falar, a autenticação/autorização multifator (vulgo 2FA, MFA, autenticação de dois fatores ou até mesmo autorização em duas etapas) consiste, resumidamente, em uma simples medida de segurança composta por uma camada extra de proteção, exigindo uma outra forma de autorização para acessar uma determinada conta ou efetuar uma determinada ação. Por exemplo, suponhamos que você possua um 2FA ativado na sua conta de e-mail. Para acessar essa conta, faz-se necessário inserir seu login e senha (sendo a senha, teoricamente, o primeiro fator de autorização). Adicionalmente, logo após a submissão do e-mail e sua respectiva senha, será solicitado um código de verificação enviado para algum outro meio de comunicação de sua posse (seja SMS, outro e-mail, ligação, etc) ou gerado através de aplicativos com essa finalidade (como Google Authentication ou Authy, entre outros) para que seja possível dar continuidade ao acesso. Desse modo, existem cinco tipos diferentes de fatores que podem ser utilizados para realizar tal validação, sendo eles:
- Algo que você é: reconhecimento facial, scan da íris, impressão digital;
- Algo que você tem: tokens de hardware ou software;
- Algo que você sabe: senha, PIN;
- Algo que você faz: assinatura a punho;
- Onde você está: localização atual, localização da origem do IP.
Essa simples camada extra tenta impedir o acesso irrestrito e não autorizado a um determinado serviço, caso por ventura sua credencial tenha sido comprometida. Vale lembrar que isso não impede que o atacante tente a mesma credencial em diversos serviços. Logo, é recomendável não realizar o uso da mesma senha para diversos serviços.
Vista a simplicidade deste modelo, pode parecer um pouco difícil imaginar alguns problemas relacionados ao mesmo. Entretanto, os problemas mais comuns com o MFA acontecem na integração da aplicação com esse sistema.
Infelizmente, é bastante comum as aplicações utilizarem o serviço de SMS (short message service) como meio de envio de código de MFA. Esse tipo de serviço é falho (no quesito de segurança) por padrão, devido a diversos fatores que não irão ser tratados neste texto. Além disso, esse serviço tem uma infraestrutura relativamente precária, onde, por exemplo, uma determinada aplicação pode apresentar a mensagem de envio do código para o número do telefone previamente cadastrado, mas o código pode demorar para fato ser recebido — impossibilitando o acesso devido a algum tempo de expiração aplicado pela aplicação — ou mesmo ele nunca ter chegado ao destinatário.
Devido a motivos como esse, acontece de nem toda aplicação ou negócio tomar a decisão de disponibilizar uma feature de 2FA mais robusta, devido a problemas de experiência do usuário e ao custo relacionado à sua manutenção. Além disso, é bastante comum encontrar aplicações que possuem brechas de segurança nessa integração: seja pela ausência de alguma validação; seja no modo como o código (ou token) foi gerado; ou mesmo na forma como este código foi enviado para o usuário.
Para tentar explanar um pouco mais sobre os problemas de segurança nessas integrações entre a aplicação e o 2FA, vejamos alguns cenários ilustrativos, mas que já foram detectados em aplicações reais, partindo do mais simples ao mais complexo.
Caso 1 — É true ou false?
Um banco fictício que iremos chamar de Banco SideChannel realizou o lançamento do seu novo internet banking — tanto para a plataforma mobile, como também para a plataforma Web. Depois de sérios problemas com fraudes, as novidades do novo sistema consistem em melhorias na segurança da aplicação, visando impedir o acesso irrestrito à conta, que passa a exigir a liberação (ou autorização) do acesso ao portador da mesma. Para isso, alguns métodos podem ser adotados, como o envio de um código por SMS ao celular associado à conta; a exigência de desbloqueio em caixa eletrônico, em um dos dispositivos com acesso à conta; ou a confirmação de informações pessoais como o nome da mãe, do pet ou do time do coração (comumente conhecidas como “respostas secretas”).
Para este primeiro caso, vamos focar apenas na funcionalidade de autenticação em conjunto com o 2FA da aplicação, e ignorar qualquer outro tipo de problema de segurança, seja ele enumeração de usuários ou ataques de força bruta.
O principal atacante do banco atentou-se para a atualização e resolveu analisar como as novas funcionalidades aplicadas estavam se comportando. Em um primeiro momento, ele percebeu que a requisição de autenticação da aplicação comporta-se da seguinte forma:
Após a requisição, a aplicação Web apresenta uma tela solicitando um código de 6 dígitos enviado para o telefone do portador da conta, como ilustrado na imagem a seguir:
Após uma breve análise, o atacante refaz a autenticação, intercepta a resposta da aplicação e realiza a alteração do campo 2FA, modificando o valor de true para false, de modo a liberar a resposta para o navegador. A partir daí, a aplicação não mais apresenta a tela de solicitação de código, mas sim, a tela principal do internet banking com a conta autenticada, todos os dados carregados normalmente e também todas as funcionalidades disponíveis.
Feito isso, o atacante resolve analisar o código do client-side da aplicação, visto que qualquer alteração no comportamento da aplicação após a modificação de uma resposta leva a crer que as validações estão sendo realizadas no lado do cliente, e não no servidor. O código utilizado na aplicação pode ser observado na figura 2:
O atacante verificou que a validação acontece através da flag retornada pela aplicação, conforme o bloco de código entre as linhas 13 e 17: se a flag vier do servidor como true, a aplicação apresentará a tela de solicitação de código.
Diante do código apresentado na figura 2, é possível perceber que a responsabilidade para saber se é necessário ou não apresentar a solicitação do 2FA dá-se no client-side. E tudo isso é feito através de uma flag retornada pelo servidor, cujo valor pode ser ‘true’ ou ‘false’.
Para melhor ilustrar o cenário, a figura 3 mais adiante, de modo semelhante a como foi demonstrado na autenticação, apresenta a requisição efetuada para o servidor, bem como a resposta do mesmo ao utilizar a funcionalidade de submissão de código 2FA. Para a requisição citada, o código enviado é válido, dado que o servidor retornou o valor true para o campo success. Perceba que a aplicação se comporta praticamente igual à funcionalidade de autenticação:
Observando a requisição de autenticação demonstrada na figura 1, vemos que o servidor já retorna um token de sessão válido antes mesmo da verificação do 2FA; e que a funcionalidade de 2FA não retorna nenhum outro novo token. Logo, podemos inferir que a mesma implementação de criação de sessão foi utilizada para liberar o acesso às contas que possuem 2FA e às contas que não possuem.
Juntando todas essas informações, podemos supor que o código da funcionalidade de autenticação da aplicação pode ser algo parecido com o código ilustrado na figura 4:
Esse tipo de implementação é comumente encontrada nas aplicações atuais, e colocam em risco seus usuários devido à facilidade de subversão da mesma. Prontamente, como já demonstrado anteriormente, é possível observar a subversão na solicitação do 2FA. Adicionalmente a isso, é bem provável que o mesmo procedimento de subversão também possa ser efetuado no envio do código de 2FA, visto que normalmente esses códigos, tal qual o trecho ilustrado anteriormente, são reaproveitados diversas vezes em uma mesma aplicação, de modo que o sistema tem um comportamento bastante parecido entre as duas funcionalidades mencionadas.
Repetindo o processo na funcionalidade de autenticação, e realizando alguns outros testes, podemos inferir que o código da funcionalidade 2FA funciona da seguinte maneira:
Levando em consideração tal código, percebe-se que, em momento algum, as ações descritas a seguir são efetuadas pela aplicação em suas operações de autenticação ou na de validação de desafio do 2FA:
- Realizar a verificação de controle de acesso às páginas da aplicação no server-side;
- Verificar se o código enviado realmente pertence ao usuário que o enviou;
- Verificar se o código enviado já foi utilizado em outro momento;
- Remover o código enviado, caso o mesmo seja válido;
- Gerar um token de sessão após a validação do 2FA e remover o já existente, confirmando que aquele usuário realmente respondeu ao 2FA corretamente.
Os pontos descritos acima são de grande relevância para uma implementação correta de 2FA, formando assim uma lista de boas práticas de segurança para impedir a subversão de tal mecanismo.
Caso 2 — Sem botão, sem acesso
Continuando com nosso Banco SideChannel, vejamos um cenário de ataque para a aplicação mobile.
Além da funcionalidade já comentada do 2FA via SMS existente na aplicação Web, a aplicação mobile possui uma funcionalidade de segurança que visa impedir o acesso irrestrito à conta, exigindo a liberação do dispositivo em algum caixa eletrônico para seu primeiro acesso. Ou seja, enquanto o dispositivo não for liberado/desbloqueado em alguma agência, não será possível utilizar as ações da aplicação mobile. A partir desse desbloqueio, um código para validação do 2FA será enviado via SMS para liberar os demais acessos no aparelho já identificado. A imagem a seguir ilustra o comportamento da aplicação ao acessar uma conta através de um dispositivo ainda não liberado:
Um erro comum neste caso, consiste em supor que, devido ao fato de a aplicação não oferecer nenhuma outra opção além do botão de sair, a etapa não apresenta perigo para o negócio. Como se a única opção neste caso fosse desistir e sair. Porém, este tipo de pensamento acaba gerando diversos problemas de segurança, como no caso da tela/activity.
De forma bastante resumida, segundo a documentação do próprio Android, “diferentemente dos paradigmas de programação em que os apps são lançados com um método main(), o sistema Android inicia o código em uma instância Activity invocando métodos de callback que correspondem a estágios específicos do ciclo de vida.”
Existem diversas maneiras para se “puxar” uma outra tela/activity além da própria interface do usuário (UI). Por exemplo, é possível “chamar” uma outra tela/activity para realizar as ações disponíveis na mesma através de hooks de função, utilizando, por exemplo, o kit de ferramentas objection¹, desenvolvido para exploração mobile em tempo de execução, com a finalidade de avaliar a postura de segurança de aplicativos móveis.
Portanto, utilizando a ferramenta mencionada, é possível realizar a listagem das activities da aplicação, e assim, realizar a chamada de uma activity qualquer através do comando a seguir:
Para o cenário do Banco SideChannel, realizou-se a chamada das activity através do seguinte comando:
Com isso, foi possível acessar toda e qualquer funcionalidade disponível na aplicação, sem a necessidade de realizar o desbloqueio no caixa eletrônico, conforme demonstrado nas imagens abaixo:
Esse problema tem a mesma característica do problema comentado no processo de autenticação, onde, após esse processo, a aplicação disponibiliza um token de sessão válido para o usuário poder então realizar toda e qualquer ação antes mesmo de realizar outra validação.
### Recomendação
Existem diversas soluções, relativamente simples, que visam impedir os problemas aqui comentados. Primeiramente, sabendo da possibilidade de o atacante possuir controle total para subverter as proteções supracitadas, é imprescindível a remoção de toda e qualquer validação de acesso (ou autorização) do client-side para operações críticas de um sistema. Essa solução deve ser generalizada para todos os casos, seja para validação de 2FA (ou MFA), ou mesmo para autenticação ou validação de uma determinada ação. Desse modo, poderá impedir, por exemplo, que uma flag seja alterada na resposta da requisição de false para true (e vice-versa); o que, como vimos, compromete a aplicação.
Essa validação pode ser feita através de tokens de sessão do usuário. Por exemplo, após a submissão do código 2FA correto para o servidor, o mesmo deve criar um novo token, retorná-lo ao usuário e remover o anterior. Ou, até mesmo, somente disponibilizar um token de sessão depois da submissão correta do código 2FA. Esse novo token de sessão irá possuir a característica de permitir que o usuário efetue a ação solicitada. Uma vez que a ação seja concluída, o token deve perder a função de autorizar a ação solicitada.
Além disso, é de suma importância que qualquer fator de autenticação e/ou autorização, apesar das suas especificações e particularidades, siga os seguintes conceitos:
- Validar sempre o desafio do MFA no lado do servidor;
- Ser de uso único (após o uso, ele deve ser descartado);
- Possuir um tempo limitado de validade (variando entre 60 e 120 segundos);
- Não ser previsível.
Vale ressaltar também, que, ao implementar um MFA, é aconselhável a utilização de fatores combinados (por exemplo, algo que você sabe, mais algo que você tem). Além disso, deve-se sempre seguir as boas práticas de segurança para a tecnologia escolhida.
Soluções de fingerprints também podem ser bastante efetivas contra cenários de fraudes ou subversão das regras de um negócio, visto que o mapeamento dessas ações é facilmente “bloqueado” após a identificação de usos inapropriados
Utilizar motores de fraudes, WAF, ou quaisquer outras ferramentas ajuda a evitar ataques contra o sistema. Mesmo assim, apesar de criarem mais camadas de proteção, elas não protegem por completo, e nem resolvem todos os problemas existentes. Por exemplo, se a aplicação permitir que o usuário selecione um número extremamente grande de parcelas na sua compra, é bem provável que essas ferramentas não consigam detectar tal ação, não podendo portanto a impedir.
Além disso, o monitoramento das ações efetuadas na aplicação também costuma ser uma abordagem bastante eficiente para a detecção de fraudes no sistema. Além de ajudar em processos de resposta a incidentes, auxilia na reavaliação das camadas de proteção da aplicação.
Como ainda existem muitos problemas relacionados às implementações de MFA (até mesmo na criação do código!), de modo a não nos estendermos demasiadamente num único blogpost, outros problemas de regra de negócio serão apresentados na continuidade desta série.
Relembramos que, apesar dos códigos aqui apresentados serem meramente ilustrativos, é bastante comum encontrar esse tipo de problema em aplicações reais nos dias atuais, tanto em pequenas, quanto em grandes corporações. É por isso que, independentemente das ferramentas utilizadas (como WAF, motores de fraude, etc.) é de suma importância realizar validações de segurança, nos ambientes utilizados e nas aplicações, de forma recorrente; ou até mesmo planejar um ciclo de segurança já na esteira de desenvolvimento. E claro, não deixar para fazer as validações/testes de segurança depois de sofrer um ataque. E se mesmo depois da leitura deste texto, você ainda achar que não vale a pena implementar uma camada a mais de segurança, com medo de comprometer a usabilidade e a experiência do usuário, visto que “a usabilidade e a experiência do usuário andam na contra-mão da segurança” (como dizem as más línguas), recomenda-se a leitura neste blogpost aqui.
### Referências
The Web Application Hacker’s Handbook – Second Edition
https://developer.android.com/guide/components/activities/intro-activities?hl=pt-br
https://authy.com/what-is-2fa/
https://trust.salesforce.com/pt/security/2fa/
https://support.google.com/accounts/answer/185839
https://github.com/sensepost/objection
https://owasp.org/www-project-top-ten/
https://owasp.org/www-project-mobile-top-10/
https://docs.oracle.com/javase/7/docs/api/java/util/Random.html