Résolveurs de contenu

Catégorie OWASP : MASVS-PLATFORM : interaction avec la plate-forme

Présentation

D'après la documentation, ContentResolver est une classe qui fournit aux applications un accès au modèle de contenu. Les résolveurs de contenu exposent des méthodes pour interagir, récupérer ou modifier le contenu fourni par :

  • les applications installées (schéma d'URI content://) ;
  • les systèmes de fichiers (schéma d'URI file://) ;
  • les API les prenant en charge et fournies par Android (schéma d'URI android.resource://).

En résumé, les failles liées à ContentResolver appartiennent à la classe confused deputy, car le pirate informatique peut utiliser les privilèges d'une application vulnérable pour accéder au contenu protégé.

Risque : utilisation abusive basée sur un URI file:// non approuvé

L'utilisation abusive de ContentResolver à l'aide de la faille de l'URI file:// exploite la capacité de ContentResolver à renvoyer les descripteurs de fichier décrits par l'URI. Cette faille affecte des fonctions comme openFile() ,openFileDescriptor(), openInputStream(), openOutputStream(), ouopenAssetFileDescriptor() à partir de l'API ContentResolver. La faille peut être utilisée de manière abusive avec un URI file:// entièrement ou partiellement contrôlé par le pirate informatique, afin de forcer l'application à accéder à des fichiers censés être non accessibles, tels que des bases de données internes ou des préférences partagées.

L'un des scénarios d'attaque possibles consiste à créer une galerie ou un sélecteur de fichiers malveillants qui, lorsqu'ils sont utilisés par une application vulnérable, renvoient un URI malveillant.

Il existe quelques variantes de cette attaque :

  • URI file:// entièrement contrôlé par le pirate qui pointe vers les fichiers internes d'une application.
  • Une partie de l'URI file:// est contrôlée par les pirates, ce qui la rend vulnérable aux traversées de répertoire.
  • Un URI file:// ciblant un lien symbolique contrôlé par le pirate qui pointe vers les fichiers internes de l'application.
  • Attaque semblable à la variante précédente, mais ici le pirate change la cible du lien symbolique de façon répétée, passant d'une cible légitime aux fichiers internes d'une application. L'objectif est d'exploiter une condition de concurrence entre un contrôle de sécurité potentiel et l'utilisation d'un chemin d'accès à un fichier.

Impact

Les conséquences de cette faille varient en fonction de l'utilisation de ContentResolver. Dans de nombreux cas, cela peut entraîner l'exfiltration de données protégées d'une application ou la modification de données protégées par des parties non autorisées.

Stratégies d'atténuation

Pour atténuer cette faille, validez le descripteur de fichier à l'aide de l'algorithme ci-dessous. Une fois la validation terminée, le descripteur de fichier peut être utilisé en toute sécurité.

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.
}


Risque : Utilisation abusive basée sur un URI content:// non approuvé

Une utilisation abusive d'un ContentResolver à l'aide d'une faille d'URI content:// se produit lorsqu'un URI contrôlé entièrement ou partiellement par un pirate informatique est transmis aux API ContentResolver pour opérer sur du contenu qui n'était pas censé être accessible.

Il existe deux scénarios principaux pour cette attaque :

  • L'application fonctionne sur son propre contenu interne. Par exemple, après avoir obtenu un URI d'un pirate informatique, l'application de messagerie associe les données de son propre fournisseur de contenu interne au lieu d'une photo externe.
  • L'application agit comme un proxy, puis accède aux données d'une autre application pour le pirate. Par exemple, l'application de messagerie joint des données de l'application X qui sont protégées par une autorisation, qui empêcherait normalement le pirate informatique de voir cette pièce jointe. Ces données sont disponibles pour l'application générant la pièce jointe, mais pas dès le début, ce qui permet de transmettre ce contenu à l'attaquant.

Un scénario d'attaque possible consiste à créer une galerie ou un sélecteur de fichiers malveillants qui, lorsqu'ils sont utilisés par une application vulnérable, renvoient un URI malveillant.

Impact

Les conséquences de cette faille varient en fonction du contexte associé à ContentResolver. Cela peut entraîner l'exfiltration de données protégées d'une application ou la modification par des tiers non autorisés de données protégées.

Stratégies d'atténuation

Général

Validez les URI entrants. Par exemple, l'utilisation d'une liste d'autorisation des autorités concernées est considérée comme une bonne pratique.

L'URI cible le fournisseur de contenu non exporté ou protégé par des autorisations appartenant à une application vulnérable

Vérifiez, si l'URI cible votre application :

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);
}

Ou si le fournisseur ciblé est exporté :

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;
}

Ou, si vous accordez une autorisation explicite à l'URI (cette vérification repose sur l'hypothèse selon laquelle si une autorisation explicite est accordée pour accéder aux données, l'URI n'est pas malveillant) :

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;
}

L'URI cible un ContentProvider protégé par des autorisations qui appartient à une autre application qui approuve l'application vulnérable.

Cette attaque est adaptée aux :

  • écosystèmes d'applications où les applications définissent et utilisent des autorisations personnalisées ou d'autres mécanismes d'authentification ;
  • attaques de proxy d'autorisation, lorsqu'un pirate informatique utilise une application vulnérable qui détient une autorisation d'exécution, telle que READ_CONTACTS, pour récupérer des données auprès d'un fournisseur de système.

Vérifiez si l'autorisation d'URI a été accordée :

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 l'utilisation d'autres fournisseurs de contenu ne nécessite pas d'autorisation (par exemple, lorsque l'application autorise toutes les applications de l'écosystème à accéder à toutes les données), interdisez explicitement l'utilisation de ces autorités.