Agentes de resolución de contenido

Categoría de OWASP: MASVS-PLATFORM: Interacción con la plataforma

Descripción general

Según la documentación, ContentResolver es una "clase que proporciona a las aplicaciones acceso al modelo de contenido". ContentResolvers expone métodos para interactuar, recuperar o modificar contenido proporcionado de lo siguiente:

  • Apps instaladas (esquema de URI content://)
  • Sistemas de archivos (esquema de URI file://)
  • Compatibilidad con APIs que proporciona Android (esquema de URI android.resource://)

En resumen, las vulnerabilidades relacionadas con ContentResolver pertenecen a la clase de difusión confundida, ya que el atacante puede usar los privilegios de una aplicación vulnerable para acceder a contenido protegido.

Riesgo: Abuso basado en el URI file:// no confiable

El abuso de ContentResolver con la vulnerabilidad del URI file:// se aprovecha de la capacidad de ContentResolver para devolver descriptores de archivos que describe el URI. Esta vulnerabilidad afecta funciones como openFile(), openFileDescriptor(), openInputStream(), openOutputStream() o openAssetFileDescriptor() de la API de ContentResolver. La vulnerabilidad se puede usar de forma indebida con un URI file:// controlado de manera total o parcial por un atacante para forzar a la aplicación a acceder a archivos a los que no se debería poder acceder, como bases de datos internas o preferencias compartidas.

Una de las posibles situaciones de ataque sería crear una galería maliciosa o un selector de archivos que, cuando una app vulnerable los use, devolvería un URI malicioso.

Hay algunas variantes de este ataque:

  • Un URI de file:// completamente controlado por el atacante que apunta a los archivos internos de una app
  • Parte del URI de file:// está controlado por atacantes, por lo que es propenso a los saltos de directorio
  • El URI de file:// dirigido a un vínculo simbólico (symlink) controlado por atacantes que apunta a los archivos internos de la app
  • Similar a la variante anterior, pero, en este caso, el atacante cambia el objetivo de symlink de forma repetida de un destino legítimo a los archivos internos de una app. El objetivo es aprovecharse de una condición de carrera entre un posible control de seguridad y el uso de rutas de archivos

Impacto

El impacto de aprovecharse de esta vulnerabilidad varía según el uso que se haga de ContentResolver. En muchos casos, puede provocar que se extraigan los datos protegidos de una app o que terceros no autorizados modifiquen los datos protegidos.

Mitigaciones

Para mitigar esta vulnerabilidad, usa el siguiente algoritmo y valida el descriptor de archivo. Después de pasar la validación, se puede usar el descriptor de archivo de forma segura.

Kotlin

fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
    // Canonicalize to resolve symlinks and path traversals.
    val fdCanonical = File(fileUri.path!!).canonicalPath

    val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)

    // Lstat doesn't follow the symlink.
    val canonicalFileStat: StructStat = Os.lstat(fdCanonical)

    // Since we canonicalized (followed the links) the path already,
    // the path shouldn't point to symlink unless it was changed in the
    // meantime.
    if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
        return false
    }

    val sameFile =
        pfdStat.st_dev == canonicalFileStat.st_dev &&
        pfdStat.st_ino == canonicalFileStat.st_ino

    if (!sameFile) {
        return false
    }

    return !isBlockedPath(ctx, fdCanonical)
}

fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
    // Paths that should rarely be exposed
    if (fdCanonical.startsWith("/proc/") ||
        fdCanonical.startsWith("/data/misc/")) {
        return true
    }

    // Implement logic to block desired directories. For example, specify
    // the entire app data/ directory to block all access.
}

Java

boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
    // Canonicalize to resolve symlinks and path traversals
    String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();

    StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());

    // Lstat doesn't follow the symlink.
    StructStat canonicalFileStat = Os.lstat(fdCanonical);

    // Since we canonicalized (followed the links) the path already,
    // the path shouldn't point to symlink unless it was changed in the meantime
    if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
        return false;
    }

    boolean sameFile =
        pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;

    if (!sameFile) {
        return false;
    }

    return !isBlockedPath(ctx, fdCanonical);
}

boolean isBlockedPath(Context ctx, String fdCanonical) {
       
        // Paths that should rarely be exposed
        if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
            return true;
        }

        // Implement logic to block desired directories. For example, specify
        // the entire app data/ directory to block all access.
}


Riesgo: Uso indebido basado en el URI de content:// no confiable

El uso indebido de un ContentResolver con una vulnerabilidad de URI de content:// ocurre cuando se pasa un URI que un atacante controla de forma total o parcial a las APIs de ContentResolver para que opere en contenido al que no se debería poder acceder.

Hay dos situaciones principales para este ataque:

  • La app opera en su propio contenido interno. Por ejemplo, después de obtener un URI de un atacante, la app de correo electrónico adjunta datos de su propio proveedor de contenido interno, en lugar de una foto externa.
  • La app actúa como un proxy y accede a los datos de otra aplicación para el atacante. Por ejemplo, la aplicación de correo electrónico adjunta datos de la app X que están protegidos por un permiso que normalmente no permitiría al atacante ver ese archivo adjunto específico. Está disponible para la aplicación que realiza el archivo adjunto, pero no al principio, por lo que se retransmite este contenido al atacante.

Una posible situación de ataque es crear una galería o un selector de archivos maliciosos que, cuando una app vulnerable los use, devolverán un URI malicioso.

Impacto

El impacto de aprovecharse de esta vulnerabilidad varía según el contexto asociado con el ContentResolver. Esto podría provocar que se extraigan los datos protegidos de una app o que grupos no autorizados modifiquen los datos protegidos.

Mitigaciones

General

Valida los URIs entrantes. Por ejemplo, se recomienda usar una lista de autoridades esperadas permitidas.

El URI se orienta al proveedor de contenido no exportado o protegido por permisos que pertenece a la app vulnerable

Verifica si el URI se orienta a tu app:

Kotlin

fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
    val authority: String = uri.authority.toString()
    val info: ProviderInfo =
        ctx.packageManager.resolveContentProvider(authority, 0)!!

    return ctx.packageName.equals(info.packageName)
}

Java

boolean belongsToCurrentApplication(Context ctx, Uri uri){
    String authority = uri.getAuthority();
    ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);

    return ctx.getPackageName().equals(info.packageName);
}

O bien, si se exportó el proveedor objetivo:

Kotlin

fun isExported(ctx: Context, uri: Uri): Boolean {
    val authority = uri.authority.toString()
    val info: ProviderInfo =
            ctx.packageManager.resolveContentProvider(authority, 0)!!

    return info.exported
}

Java

boolean isExported(Context ctx, Uri uri){
    String authority = uri.getAuthority();
    ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);      

    return info.exported;
}

O si se otorga permiso explícito al URI, esta verificación se basa en la suposición de que si se otorga permiso explícito para acceder a los datos, el URI no es malicioso:

Kotlin

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
    val pid: Int = Process.myPid()
    val uid: Int = Process.myUid()
    return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
            PackageManager.PERMISSION_GRANTED
}

Java

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
    int pid = Process.myPid();
    int uid = Process.myUid();

    return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}

El URI se orienta a un ContentProvider protegido por permisos que pertenece a otra app que confía en la app vulnerable.

Este ataque es relevante en las siguientes situaciones:

  • Ecosistemas de aplicaciones en los que las apps definen y usan permisos personalizados o, también, otros mecanismos de autenticación
  • Ataques de proxy de permiso, en los que un atacante hace uso indebido de una app vulnerable que tiene un permiso de tiempo de ejecución, como READ_CONTACTS, para recuperar datos de un proveedor del sistema

Prueba si se otorgó el permiso de URI:

Kotlin

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
    val pid: Int = Process.myPid()
    val uid: Int = Process.myUid()
    return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
            PackageManager.PERMISSION_GRANTED
}

Java

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
    int pid = Process.myPid();
    int uid = Process.myUid();

    return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}

Si el uso de otros proveedores de contenido no requiere una concesión de permisos, como cuando la app permite que todas las apps del ecosistema accedan a todos los datos, entonces prohíbe de forma explícita el uso de estas autoridades.