Risolutori di contenuti

Categoria OWASP: MASVS-PLATFORM: Platform Interaction

Panoramica

Secondo la documentazione, ContentResolver è una "classe che fornisce alle applicazioni l'accesso al modello di contenuti". I ContentResolver espongono metodi per interagire, recuperare o modificare i contenuti forniti da:

  • App installate (schema URI content://)
  • File system (schema URI file://)
  • Supporto delle API fornite da Android (schema URI android.resource://).

In sintesi, le vulnerabilità relative a ContentResolver appartengono alla classe confused deputy, in quanto l'autore dell'attacco può utilizzare i privilegi di un'applicazione vulnerabile per accedere a contenuti protetti.

Rischio: abuso basato su URI file:// non attendibile

L'abuso di ContentResolver che utilizza la vulnerabilità dell'URI file:// sfrutta la capacità di ContentResolver di restituire i descrittori di file descritti dall'URI. Questa vulnerabilità interessa funzioni come openFile(), openFileDescriptor(), openInputStream(), openOutputStream() o openAssetFileDescriptor() dell'API ContentResolver. La vulnerabilità può essere sfruttata con un URI file:// controllato completamente o parzialmente dall'autore dell'attacco per forzare l'applicazione ad accedere a file che non dovevano essere accessibili, come database interni o preferenze condivise.

Uno dei possibili scenari di attacco sarebbe quello di creare una Galleria o un selettore file dannosi che, se utilizzati da un'app vulnerabile, restituirebbero un URI dannoso.

Esistono diverse varianti di questo attacco:

  • URI file:// completamente controllato dall'autore dell'attacco che punta ai file interni di un'app
  • Parte dell'URI file:// è controllata dall'autore dell'attacco, il che la rende soggetta a path traversal
  • URI file:// che ha come target un collegamento simbolico (symlink) controllato da un malintenzionato che punta ai file interni dell'app
  • Simile alla variante precedente, ma in questo caso l'autore dell'attacco scambia ripetutamente la destinazione del link simbolico da una destinazione legittima ai file interni di un'app. L'obiettivo è sfruttare una race condition tra un potenziale controllo di sicurezza e l'utilizzo del percorso del file

Impatto

L'impatto dello sfruttamento di questa vulnerabilità varia a seconda dell'utilizzo di ContentResolver. In molti casi, può comportare l'esfiltrazione dei dati protetti di un'app o modifiche dei dati protetti da parte di soggetti non autorizzati.

Mitigazioni

Per mitigare questa vulnerabilità, utilizza l'algoritmo riportato di seguito per convalidare il descrittore del file. Dopo aver superato la convalida, il descrittore del file può essere utilizzato in sicurezza.

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


Rischio: abuso basato sull'URI content:// non attendibile

L'abuso di un ContentResolver utilizzando una vulnerabilità URI content:// si verifica quando un URI controllato completamente o parzialmente da un utente malintenzionato viene trasmesso alle API ContentResolver per operare su contenuti che non dovevano essere accessibili.

Esistono due scenari principali per questo attacco:

  • L'app funziona con i propri contenuti interni. Ad esempio, dopo aver ricevuto un URI da un malintenzionato, l'app di posta allega i dati del proprio fornitore di contenuti interno anziché una foto esterna.
  • L'app funge da proxy e poi accede ai dati di un'altra applicazione per l'aggressore. Ad esempio, l'applicazione di posta allega dati dell'app X protetti da un'autorizzazione che normalmente impedirebbe all'attaccante di vedere quell'allegato specifico. È disponibile per l'applicazione che esegue l'allegato, ma non inizialmente, quindi trasmette questi contenuti all'autore dell'attacco.

Uno scenario di attacco possibile è la creazione di una galleria o di un selettore file dannosi che, se utilizzati da un'app vulnerabile, restituirebbero un URI dannoso.

Impatto

L'impatto dello sfruttamento di questa vulnerabilità varia a seconda del contesto associato a ContentResolver. Ciò potrebbe comportare l'esfiltrazione o modifiche ai dati protetti di un'app da parte di soggetti non autorizzati.

Mitigazioni

Generale

Convalida gli URI in entrata. Ad esempio, l'utilizzo di una lista consentita di autorità previste è considerato una buona pratica.

L'URI ha come target un fornitore di contenuti non esportato o protetto da autorizzazioni appartenente a un'app vulnerabile

Controlla se l'URI ha come target la tua 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);
}

Oppure, se viene esportato il fornitore scelto come target:

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

Oppure se è stata concessa l'autorizzazione esplicita all'URI. Questo controllo si basa sul presupposto che, se è stata concessa l'autorizzazione esplicita per accedere ai dati, l'URI non è dannoso:

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 ha come target un ContentProvider protetto da autorizzazioni che appartiene a un'altra app che considera attendibile l'app vulnerabile.

Questo attacco è pertinente alle seguenti situazioni:

  • Ecosistemi di applicazioni in cui le app definiscono e utilizzano autorizzazioni personalizzate o altri meccanismi di autenticazione.
  • Attacchi proxy di autorizzazione, in cui un malintenzionato abusa di un'app vulnerabile che dispone di un'autorizzazione di runtime, ad esempio READ_CONTACTS, per recuperare dati da un provider di sistema.

Verifica se l'autorizzazione URI è stata concessa:

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

Se l'utilizzo di altri fornitori di contenuti non richiede la concessione di un'autorizzazione, ad esempio quando l'app consente a tutte le app dell'ecosistema di accedere a tutti i dati, vieta esplicitamente l'utilizzo di queste autorità.