Olá pessoal, hoje eu gostaria de falar sobre algumas opções para transferir dados entre dois aplicativos Android e considerá-los do ponto de vista da segurança. Decidi escrever este artigo por duas razões. Primeiro, muitas vezes comecei a encontrar uma falta de entendimento dos desenvolvedores dos mecanismos para trabalhar com os componentes de um aplicativo Android. A segunda - parei de entender em que base esse mecanismo ou aquele se baseia na implementação de recursos e queria transmitir como ele deveria parecer no mínimo.
Desafio
Temos 2 aplicativos que acessam a mesma API. Os clientes podem acessar a API acessando o token (sessionId). Você deve implementar uma transição contínua de um aplicativo para outro. Para fazer isso, você precisa se atrapalhar entre eles, por exemplo, que seja sessionId.
Opção 1: QUERY DEEPLINK
A opção mais óbvia é transferir o token para o Query DeepLink. Será algo parecido com isto:
slave://main?sessionId=686A885A4FB644053C584B9BE2A70C7D
Nesse caso, o destinatário poderá extrair o sessionId e usá-lo sem solicitar autorização do usuário. Do lado do desenvolvedor, parece que a tarefa foi concluída, mas vamos nos aprofundar um pouco mais.
Seqüestro profundo
Como qualquer aplicativo pode registrar o esquema tinkoff: //, o sistema operacional pode abrir o aplicativo errado. Isso é possível devido ao fato de não haver registro e restrições no uso de esquemas. Um aplicativo mal-intencionado pode registrar o esquema tinkoff: // e interceptar a solicitação no aplicativo Tinkoff e iniciar-se. Nesse caso, o sessionId cairá nas mãos erradas e sua conta será comprometida. Além disso, o DeepLink Hijacking permite realizar phishing, por exemplo, exibindo campos para inserir um nome de usuário e senha.
Conceitualmente, o processo fica assim:

Existem 2 soluções para este problema. Primeiro, a tecnologia AppLinks não permite mais que os desenvolvedores personalizem o esquema; em vez disso, o http / https é usado. Nesse caso, o sistema operacional pega o link
slave.com/profile e entra em contato com o host slave.com para verificação. O segundo - URL de intenção - em vez de chamar slave: //, intent: // é chamado, onde o identificador exclusivo do aplicativo a ser iniciado é passado. É assim:
intent://main/#Intent;scheme=slave;package=com.example.slave.client.android;end"
Nesse caso, não será possível interceptar o lançamento do aplicativo, pois um pacote específico está especificado. Ainda assim, o problema é que o usuário pode instalar o aplicativo a partir de uma fonte de terceiros com o mesmo packageId que o seu escravo. Nesse caso, se você não tiver um aplicativo escravo legítimo, o aplicativo malicioso instalará e receberá seu token.
Fixação de sessão
É um ataque no qual um invasor força o cliente a estabelecer uma sessão com o software de destino usando o sessionId fornecido pelo invasor. Assim que o usuário se autenticar, o invasor poderá usar esse identificador já privilegiado para seus próprios fins. O ataque explora o fato de o software de destino usar a mesma sessionId após a escalação de privilégios.

Como fica no nosso caso:
- invasor recebe sessão anônima do aplicativo
- lança lindamente uma carta para a vítima em nome do banco, na qual ele é convidado a ir para sua conta pessoal
- ao clicar no link, chegamos ao DeepLink com um escravo da sessão do invasor: // main? sessionId = 686A885A4FB644053C584B9BE2A70C7D
- o aplicativo móvel realiza uma sessão, entende que não possui direitos suficientes e solicita que o usuário se autentique
- o usuário passa, a sessão aumentou os direitos
- usuário no aplicativo, um invasor com uma sessão privilegiada, lucro
Seria correto corrigir isso na API, emitindo outra sessionId após a escalação de privilégios, mas estamos escrevendo um aplicativo móvel. E nosso caminho é recusar transferir o token de mestre para escravo. Além disso, isso nos dará uma proteção profunda e, se algo quebrar na API e os tokens não mudarem quando os privilégios forem aumentados, um ataque ainda será impossível.
Vazamento de terceiros
Outro ponto negativo desta opção. Muitas pessoas usam serviços de terceiros para o DeepLink devido à conveniência de gerar links, análises e outras coisas interessantes. Nesse caso, você simplesmente entrega seu token a uma empresa terceirizada.
Opção 2: FORNECEDOR DE CONTEÚDO
Como vamos fazer isso? Definimos o provedor de conteúdo principal e fazemos o escravo ir para esse provedor de conteúdo para obter o token.

Assim, eliminamos o risco de transferir o token para o aplicativo errado no caso do DeepLink Hijacking e tornamos o ataque de fixação de sessão impossível. Mas temos outros problemas - na versão atual, em geral, qualquer aplicativo pode solicitar um token a qualquer momento, mesmo que não tenhamos iniciado seu lançamento.
Nível de proteção
Na maioria dos casos, é necessário verificar se o escravo está assinado com a mesma chave que o mestre, ou seja, eles pertencem ao mesmo autor. Nesse caso, o gerenciador de pacotes possui um método checkSignatures que verifica assinaturas de aplicativos. Para usar esta função, você precisa adicionar permissão com protectionLevel = "signature" no provedor de conteúdo no manifesto do aplicativo:
<permission android:name="com.example.contentprovider.access" android:protectionLevel="signature"/> <application> <provider ... android:readPermission="com.example.contentprovider.access"> </application>
O esquema dificilmente será alterado em relação à figura anterior, apenas uma garantia aparecerá de que apenas aplicativos com uma assinatura do mesmo autor terão acesso ao token.
Condição de corrida de permissão
Há um recurso muito desagradável de que os nomes de permissão não são exclusivos, que podem ser usados por um aplicativo mal-intencionado e registram permissão com nosso nome e protectionLevel = "normal" diante de nós. Nesse caso, ao instalar nosso aplicativo, a permissão já existirá no sistema operacional e não será substituída. Conseqüentemente, nosso provedor de conteúdo permanecerá desprotegido e com acesso autorizado a partir de qualquer aplicativo.
Assinaturas diferentes
Infelizmente, os aplicativos nem sempre são assinados com a mesma chave; por exemplo, alguns são comprados, ou “tão historicamente”, mas ainda é necessária uma transição contínua. Nesse caso, fazemos a verificação de assinatura por conta própria.
Como isso pode ser implementado:
O Content-Provider possui um método getCallingPackage (), pelo qual podemos obter o packageId do aplicativo que solicitou dados, e pelo packageId podemos obter a lista de assinaturas e verificá-las com as internas.
String pkg = this.getCallingPackage(); PackageInfo pkgInfo = pkgmgr.getPackageInfo(pkg, GET_SIGNATURES); Signatures[] signatures = pkgInfo.signatures; for (Signature sig: signatures) { if (sig.equals(TRUSTED_SIGNATURE)) {

Parece que fizemos tudo perfeitamente, mas não.
Vulnerabilidade de identificação falsa
O problema é que, quando o Android cria uma cadeia de confiança, o processo de verificação compara apenas o assunto e não verifica a assinatura no campo do assinante do certificado. Como resultado, um invasor pode criar uma cadeia de confiança sem uma assinatura real.
Devido a esse erro, é gerada uma cadeia de certificados incorreta, que pode incluir certificados legítimos incorporados no APK, mas na verdade não é usada para assinar o aplicativo. No final, deixarei um link para a confirmação que corrige essa vulnerabilidade. O problema foi corrigido no Android 4.4, portanto, podemos aumentar o nível da API para 19.
Conclusões
Hoje examinamos como os recursos devem ser analisados durante o desenvolvimento.
Também examinamos as opções para transferir o segredo entre dois aplicativos, durante os quais analisamos os problemas de cada opção e criamos maneiras de evitá-los.
Todos os aplicativos seguros!
Referências