Todos os artigos
Segurança · 5 min de leitura

Análise estática no Android – parte 3: manifesto e superfície de ataque

O que o AndroidManifest.xml revela depois de decodificado: componentes exportados, deep links, task hijacking, network security config e permissões.

Série · Parte 6 de 11 Segurança Mobile
  1. 01 O mapa da segurança mobile: OWASP MASVS, MASWE e MASTG 6 min
  2. 02 Arquitetura do Android: do kernel ao app 10 min
  3. 03 Arquitetura do iOS: do boot ao Secure Enclave 7 min
  4. 04 Análise estática no Android – parte 1: abrindo o APK 6 min
  5. 05 Análise estática no Android – parte 2: segredos no binário 4 min
  6. 06 Análise estática no Android – parte 3: manifesto e superfície de ataque 5 min
  7. 07 Análise estática no Android – parte 4: TLS, pinning e armazenamento 5 min
  8. 08 Scanners automáticos: o que eles pegam e o que passa batido 4 min
  9. 09 Análise estática no iOS: binário, Info.plist e entitlements 5 min
  10. 10 Análise dinâmica: o app rodando sob instrumentação 5 min
  11. 11 Do achado ao relatório: confirmar, classificar e registrar 4 min
Sobre a série
Neste artigo8 seções
  1. O básico: flags da tag <application>
  2. Componentes exportados
  3. Deep links: a porta mais usada
  4. Hijacking de tarefa
  5. Network Security Config
  6. FileProvider
  7. Permissões
  8. Juntando as peças

O AndroidManifest.xml é o documento que descreve um app para o sistema: quais componentes ele tem, do que precisa e com quem está disposto a conversar. Depois de decodificado (veja o artigo sobre como abrir um APK), ele também é o primeiro lugar que um teste de segurança revisa, porque ali está declarada boa parte da superfície de ataque de um app: tudo que outro processo, outro app ou o próprio sistema consegue acionar sem precisar decompilar nada.

O básico: flags da tag <application>

Algumas flags simples do elemento <application> já definem proteções fundamentais:

Flag O que faz Esperado em produção
android:debuggable Permite anexar um debugger ao processo do app Ausente ou false
android:allowBackup Permite que o adb backup copie os dados do app false (sem o atributo, o padrão do Android é true)
android:usesCleartextTraffic Permite tráfego HTTP sem criptografia Ausente ou false

Dá para checar as três de uma vez, a partir do manifesto já decodificado pelo apktool (veja o artigo sobre abrir um APK):

cd apktool-out
grep -oE 'android:(debuggable|allowBackup|usesCleartextTraffic|networkSecurityConfig)="[^"]*"' AndroidManifest.xml

Um app com debuggable="true" publicado na loja permite que qualquer pessoa com o APK conecte um debugger ao processo em execução, inspecione memória e até execute código arbitrário no contexto do app. O Android exige que apps de produção sejam compilados sem essa flag desde que ela existe; builds de desenvolvimento costumam ativá-la automaticamente.

Componentes exportados

Um app Android é feito de quatro tipos de componente: activities (telas), services (processos em segundo plano), broadcast receivers (reagem a eventos do sistema) e content providers (compartilham dados estruturados). Qualquer um deles pode ser declarado como exportado (android:exported="true"), o que significa, na prática, "outros apps instalados no aparelho podem chamar este componente diretamente".

Isso não é, por si só, um problema: muitos componentes existem justamente para serem chamados de fora (uma tela que abre a partir de uma notificação, por exemplo). O risco aparece quando um componente exportado executa uma ação sensível (ler dados, fazer uma chamada de rede, mudar um estado) sem verificar quem está chamando. Nesse caso, qualquer app malicioso instalado no mesmo aparelho pode acionar essa ação, mesmo sem nenhuma permissão especial.

Desde o Android 12, declarar explicitamente android:exported passou a ser obrigatório para qualquer componente com um filtro de intent, o que reduziu a quantidade de exportações acidentais. Mas o atributo pode continuar sendo definido como true de propósito, e cabe à revisão confirmar se cada um desses componentes valida devidamente quem o está chamando. Para listar todos:

grep -oE '<(activity|service|receiver|provider)[^>]*android:exported="true"[^>]*>' AndroidManifest.xml \
  | grep -oE 'android:name="[^"]*"' | sort -u

Cada componente da própria lista do app (não de uma biblioteca) vira candidato a testar de verdade mais à frente, na análise dinâmica.

Deep links são URLs que abrem diretamente uma tela do app, em vez do navegador. Eles aparecem no manifesto como intent filters numa activity, e vêm em dois formatos:

  • Links HTTPS verificados (Android App Links), quando a activity declara android:autoVerify="true" para um domínio https. Sem esse atributo, o sistema não confirma que o app é o dono legítimo do domínio, e mais de um app pode disputar o mesmo link, abrindo uma caixa de seleção para o usuário ou, em alguns casos, deixando o app errado capturar o link.
  • Esquemas customizados (meuapp://algumacoisa), que não passam por nenhuma verificação de domínio. Qualquer app pode declarar o mesmo esquema.

Em qualquer um dos dois formatos, todo parâmetro que chega por um deep link é entrada não confiável: nada garante que ele veio de um link gerado pelo próprio backend do app, em vez de um link malicioso enviado por mensagem ou outro app. Para listar as activities que aceitam links abertos no navegador (BROWSABLE) e checar se têm autoVerify:

import re
m = open('AndroidManifest.xml').read()
for a in re.findall(r'<activity[^>]*>.*?</activity>', m, re.S):
    if 'BROWSABLE' in a:
        print(re.sub(r'\s+', ' ', a)[:1500])

Hijacking de tarefa

Um comportamento menos conhecido é o task hijacking, descrito originalmente num artigo acadêmico de 2015, que explora a forma como o Android organiza telas em "tarefas" (pilhas de activities). Uma activity configurada com android:launchMode="singleTask" sem um taskAffinity próprio pode, em cenários específicos, ter sua tarefa sequestrada por outro app malicioso, que insere uma tela falsa (de phishing, por exemplo) por cima da tarefa legítima. O Google reduziu essa superfície em versões mais recentes do Android, mas a combinação dessas duas flags continua valendo a pena revisar:

grep -oE 'android:(taskAffinity|launchMode)="[^"]*"' AndroidManifest.xml | sort | uniq -c

Network Security Config

Desde o Android 7, apps podem declarar um Network Security Config, um arquivo XML que controla, por domínio, como o app se comporta na rede. Alguns pontos que costumam aparecer numa revisão:

  • cleartextTrafficPermitted="true" para qualquer domínio que não seja de teste local é tráfego sem TLS permitido explicitamente.
  • <certificates src="user" /> faz o app confiar em certificados instalados manualmente pelo usuário, o que inclui o certificado de um proxy de interceptação, usado tanto por quem analisa o app quanto por um atacante numa rede comprometida.
  • Um bloco de certificate pinning (<pin-set>) com expiration vencida para de ser aplicado silenciosamente, porque o Android ignora pins expirados.
cat res/xml/network_security_config.xml

FileProvider

Apps que compartilham arquivos com outros apps (para abrir um PDF, por exemplo) costumam usar um FileProvider, configurado por um XML próprio que define quais pastas podem ser expostas por URI. Um path="." dentro de uma tag como external-path, files-path ou cache-path libera a pasta inteira, em vez de só o arquivo pretendido; se um URI for concedido por engano a outro app, ele ganha acesso de leitura a tudo que estiver ali dentro.

cat res/xml/*paths*.xml

Permissões

Por fim, o manifesto lista todas as permissões que o app solicita:

grep -oE 'uses-permission android:name="[^"]*"' AndroidManifest.xml | sed 's/.*name="//;s/"//' | sort -u

Nem toda permissão pedida é suspeita, mas algumas costumam exigir justificativa clara na política de privacidade e na ficha da loja, caso apareçam num app sem uma razão óbvia: QUERY_ALL_PACKAGES (lista todos os apps instalados no aparelho), READ_PHONE_STATE e SYSTEM_ALERT_WINDOW (desenhar por cima de outros apps).

Juntando as peças

Nenhuma dessas configurações, isolada, costuma ser uma falha grave. O que um teste de segurança procura é a combinação: um componente exportado que lê um arquivo, um FileProvider liberal apontando para a pasta errada, um deep link que alimenta esse componente com dado não confiável. O manifesto é, essencialmente, o mapa de onde essas combinações podem existir.

No próximo artigo da série: o que muda quando se olha o código nativo do app, da forma como ele valida (ou não valida) o certificado TLS até o que fica salvo, sem proteção, no armazenamento do aparelho.

Leia também