Considera impropriamente attendibile il nome file fornito da ContentProvider

Categoria OWASP: MASVS-CODE: Code Quality

Panoramica

FileProvider, una sottoclasse di ContentProvider, ha lo scopo di fornire un metodo sicuro per un'applicazione ("applicazione server") per condividere file con un'altra applicazione ("applicazione client"). Tuttavia, se l'applicazione client non gestisce correttamente il nome file fornito dall'applicazione server, un'applicazione server controllata da un malintenzionato potrebbe essere in grado di implementare il proprio FileProvider dannoso per sovrascrivere i file nello spazio di archiviazione specifico dell'app dell'applicazione client.

Impatto

Se un malintenzionato può sovrascrivere i file di un'applicazione, ciò può portare all'esecuzione di codice dannoso (sovrascrivendo il codice dell'applicazione) o consentire di modificare in altro modo il comportamento dell'applicazione (ad esempio, sovrascrivendo le preferenze condivise dell'applicazione o altri file di configurazione).

Mitigazioni

Non considerare attendibile l'input utente

Preferisci lavorare senza input dell'utente quando utilizzi chiamate al file system generando un nome file univoco quando scrivi il file ricevuto nell'archiviazione.

In altre parole, quando l'applicazione client scrive il file ricevuto nell'archivio, deve ignorare il nome file fornito dall'applicazione server e utilizzare il proprio identificatore univoco generato internamente come nome file.

Questo esempio si basa sul codice disponibile all'indirizzo https://developer.android.com/training/secure-file-sharing/request-file:

Kotlin

// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)

try {
    val inputStream = FileInputStream(fd)
    val tempFile = File.createTempFile("temp", null, cacheDir)
    val outputStream = FileOutputStream(tempFile)
    val buf = ByteArray(1024)
    var len: Int
    len = inputStream.read(buf)
    while (len > 0) {
        if (len != -1) {
            outputStream.write(buf, 0, len)
            len = inputStream.read(buf)
        }
    }
    inputStream.close()
    outputStream.close()
} catch (e: IOException) {
    e.printStackTrace()
    Log.e("MainActivity", "File copy error.")
    return
}

Java

// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)

FileInputStream inputStream = new FileInputStream(fd);

// Create a temporary file
File tempFile = File.createTempFile("temp", null, getCacheDir());

// Copy the contents of the file to the temporary file
try {
    OutputStream outputStream = new FileOutputStream(tempFile))
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer)) > 0) {
        outputStream.write(buffer, 0, length);
    }
} catch (IOException e) {
    e.printStackTrace();
    Log.e("MainActivity", "File copy error.");
    return;
}

Eliminazione dei nomi file forniti

Sanitizza il nome file fornito durante la scrittura del file ricevuto nello spazio di archiviazione.

Questa mitigazione è meno auspicabile di quella precedente perché può essere difficile gestire tutti i potenziali casi. Tuttavia, se la generazione di un nome file univoco non è pratica, l'applicazione client deve sanificare il nome file fornito. La sanificazione include:

  • Pulizia dei caratteri di attraversamento del percorso nel nome file
  • Eseguire una canonizzazione per verificare che non ci siano attraversamenti di percorsi

Questo codice di esempio si basa sulle indicazioni per recuperare le informazioni sui file:

Kotlin

protected fun sanitizeFilename(displayName: String): String {
    val badCharacters = arrayOf("..", "/")
    val segments = displayName.split("/")
    var fileName = segments[segments.size - 1]
    for (suspString in badCharacters) {
        fileName = fileName.replace(suspString, "_")
    }
    return fileName
}

val displayName = returnCursor.getString(nameIndex)
val fileName = sanitizeFilename(displayName)
val filePath = File(context.filesDir, fileName).path

// saferOpenFile defined in Android developer documentation
val outputFile = saferOpenFile(filePath, context.filesDir.canonicalPath)

// fd obtained using Requesting a shared file from Android developer
// documentation

val inputStream = FileInputStream(fd)

// Copy the contents of the file to the new file
try {
    val outputStream = FileOutputStream(outputFile)
    val buffer = ByteArray(1024)
    var length: Int
    while (inputStream.read(buffer).also { length = it } > 0) {
        outputStream.write(buffer, 0, length)
    }
} catch (e: IOException) {
    // Handle exception
}

Java

protected String sanitizeFilename(String displayName) {
    String[] badCharacters = new String[] { "..", "/" };
    String[] segments = displayName.split("/");
    String fileName = segments[segments.length - 1];
    for (String suspString : badCharacters) {
        fileName = fileName.replace(suspString, "_");
    }
    return fileName;
}

String displayName = returnCursor.getString(nameIndex);
String fileName = sanitizeFilename(displayName);
String filePath = new File(context.getFilesDir(), fileName).getPath();

// saferOpenFile defined in Android developer documentation

File outputFile = saferOpenFile(filePath,
    context.getFilesDir().getCanonicalPath());

// fd obtained using Requesting a shared file from Android developer
// documentation

FileInputStream inputStream = new FileInputStream(fd);

// Copy the contents of the file to the new file
try {
    OutputStream outputStream = new FileOutputStream(outputFile))
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer)) > 0) {
        outputStream.write(buffer, 0, length);
    }
} catch (IOException e) {
    // Handle exception
}

Collaboratori: Dimitrios Valsamaras e Michael Peck di Microsoft Threat Intelligence

Risorse