Antônio Martins: Certificate Pinning Bypass - Android SSL Unpinning - Tempest Blog
Por Antônio Martins em 2015-04-09 11:53:02 0 comentários
A análise do tráfego de uma aplicação mobile é essencial para a identificação de possíveis vulnerabilidades decorrentes, por exemplo, do vazamento de informação e da manipulação das requisições, além de também ser útil para o entendimento do negócio da aplicação. Tal análise e manipulação, geralmente é feita através do uso de uma ferramenta de proxy web. Entretanto, quando a aplicação faz uso de HTTPS (SSL/TLS) em conjunto com Certificate Pinning, simplesmente não é possível interceptar o tráfego em texto-plano, uma vez que não possuímos em nosso proxy o certificado/chave pública do servidor com o qual a aplicação se conecta. Para driblar esse cenário, desenvolvemos uma ferramenta cujo objetivo é contornar (bypassar) as verificações feitas pelo Android no momento em que são realizadas as verificações do HTTPS, permitindo assim interceptar qualquer requisição HTTPS realizada pela aplicação.
Este post tem como objetivo detalhar o método utilizado para bypassar a técnica de Certificate Pinning em um ambiente de análise de aplicação mobile controlado.
Certificate Pinning é uma técnica que visa proteger a aplicação contra ataques do tipo man-in-the-middle (MITM). Para isso, a CA (Certificate Authority) ou chave pública do servidor é fixada na aplicação, fazendo com que a mesma não faça uso das CAs existentes/adicionadas no dispositivo. Você pode obter mais detalhes desta técnica em meu post anterior: Certificate Pinning - Dificultando ataques MITM contra sua aplicação mobile.
Android/Java - SSL/TLS
A fim de garantir a confidencialidade e a integridade dos dados transferidos entre a aplicação mobile e o servidor, o ambiente Android, através do Java, disponibiliza em sua arquitetura a mesma estrutura/framework existente no desenvolvimento de aplicações web tradicionais. O JSSE - Java Secure Socket Extension, que implementa as versões Java do SSL e TLS presentes nos pacotes javax.net e javax.net.ssl, fornece aos desenvolvedores uma API que possibilita a implementação de um canal seguro para a transferência de dados entre o cliente e o servidor sobre TCP/IP.
Arquitetura Android em níveis:
Fonte: http://developer.android.com
Exemplo de Pinning
Em sua concepção mais simples, a implementação de uma requisição HTTPS utilizando Certificate Pinning, na versão chave pública hardcoded, precisa implementar uma interface do tipo trust manager, especificamente a interface X509TrustManager, utilizada para certificados X509 e disponível no pacote javax.net.ssl. Esse trust manager é responsável por realizar a autenticação dos certificados e, para tal, ele dispõe de dois métodos: checkClientTrusted e checkServerTrusted. Uma vez implementado o trust manager, o próximo passo é preparar um canal seguro através da classe SSLContext, utilizando como parâmetro de inicialização o nosso trust manager. Dessa forma, ao realizar uma requisição através da classe HttpsURLConnection, podemos usar o método setSSLSocketFactory para adicionarmos nosso SSLSocketFactory, o qual vai tentar criar o túnel SSL utilizando como verificador nossa chave pública hardcoded. A seguir temos um exemplo desta implementação:
Em seguida, se adicionarmos a CA da nossa ferramenta de proxy no dispositivo, e tentarmos interceptar as requisições HTTPS realizadas pela aplicação, receberemos uma mensagem de erro, comprovando a eficácia do Certificate Pinning:
O código da aplicação de testes/exemplo está disponível no seguinte repositório: https://github.com/ac-pm/CertPinning
Contornando o Certificate Pinning
Uma vez conhecendo o mecanismo de checagem utilizado na implementação de uma conexão SSL, uma forma de contornar tais verificações é alterar o código fonte destas APIs no projeto Android. Para tal, necessitaríamos baixar todo o código fonte do Android na sua versão AOSP - Android Open Source Project, realizar as alterações, gerar a imagem e instalar (flashear) no dispositivo.
Outra forma de contornar o Pinning porém, bem mais simples que a solução descrita acima, é através do hook de funções. A técnica de hook possibilita que no momento em que ocorra uma determinada chamada de função, a mesma tenha seu conteúdo alterado. Em nosso projeto Android, estamos utilizando o Cydia Substrate, que é a API utilizada pelo famoso Cydia - Apple App Store, que tem sua versão da API disponível para Android.
A API do projeto Cydia Substrate, desenvolvida por Jay Freeman (saurik), possibilita realizar os hooks com pouco código, precisamos apenas utilizar o método hookClassLoad para detectar o momento em que a classe alvo é executada, e o método hookMethod que permite a manipulação das ações do método alvo.
Observação: Para utilizar a API do Cydia Substrate, se faz necessário a instalação do Cydia em um dispositivo "rooteado".
Projeto "SSL Unpinning"
Uma vez conhecendo nosso objetivo e os meios para o bypass do Certificate Pinning, foi desenvolvido um aplicativo chamado SSL Unpinning, cujo objetivo é permitir que o analista escolha uma aplicação específica para contornar o seu pinning.
A implementação é bem simples e o seu código está disponível no seguinte repositório: https://github.com/ac-pm/SSLUnpinning
A seguinte imagem ilustra parte do código da aplicação que faz uso de Pinning. Como podemos ver, a aplicação utiliza a implementação básica para uma requisição HTTPS, como mencionada na seção Android/Java - SSL/TLS:
Para contornar as verificações realizadas pela implementação acima, os seguintes métodos foram alterados através de hooks:
Método: javax.net.ssl.HttpsURLConnection.setSSLSocketFactory
O setSSLSocketFactory receberá um SSLSocketFactore cujo SSLContext recebeu um X509TrustManager alterado que não fará suas devidas verificações, pois seus métodos checkClientTrusted e checkServerTrusted estão vazios! As seguintes imagens ilustram o hook e a implementação do empty trust manager.
EmptyTrustManager que implementa a interface X509TrustManager:
Método: javax.net.ssl.TrustManagerFactory.getTrustManagers
Retorna nossa implementação "vazia" do X509TrustManager.
Método: javax.net.ssl.SSLContext.init
Inicia o SSLContext com nosso trust manager EmptyTrustManager cujos métodos checkClientTrusted e checkServerTrusted estão vazios.
Método: javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier
O método setDefaultHostnameVerifier receberá como parâmetro a flag ALLOW_ALL_HOSTNAME_VERIFIER, que desabilita a verificação do nome do host existente no certificado X.509.
A partir deste momento, as verificações do Pinning foram contornadas e a aplicação exemplo, apresentada anteriormente, já pode ter suas requisições interceptadas. As seguintes imagens ilustram a escolha do aplicativo alvo através da ferramenta SSL Unpinning e o momento em que o tráfego da aplicação é interceptado pela ferramenta de proxy:
Outras implementações do HTTPS
Assim como a aplicação exemplo, boa parte das aplicações utilizam a classe HttpsURLConnection do pacote javax.net.ssl, contudo, o Android possibilita a utilização de outras opções de classes/pacotes, por exemplo, o Volley do Google (com.android.volley) ou o Android Asynchronous Http Client (com.loopj.android) do James Smith (loopj). Desta forma, para que o bypass da técnica de Pinning funcione também nesses casos, as implementações dessas outras opções devem ser estudadas, e os devidos hooks implementados.
Observação: Este projeto tem como base a ferramenta SSL Trust Killer, desenvolvida pela iSEC Partners.