Nicht vertrauenswürdiger, vom ContentProvider bereitgestellter Dateiname

OWASP-Kategorie:MASVS-CODE: Code Quality

Übersicht

FileProvider, eine Unterklasse von ContentProvider, soll eine sichere Methode für eine Anwendung („Serveranwendung“) zum Freigeben von Dateien für eine andere Anwendung („Clientanwendung“) bieten. Wenn die Clientanwendung den vom Server bereitgestellten Dateinamen jedoch nicht richtig verarbeitet, kann eine vom Angreifer kontrollierte Serveranwendung einen eigenen schädlichen FileProvider implementieren, um Dateien im anwendungsspezifischen Speicher der Clientanwendung zu überschreiben.

Positiv beeinflussen

Wenn ein Angreifer die Dateien einer Anwendung überschreiben kann, kann dies zur Ausführung von Schadcode führen (durch Überschreiben des Anwendungscodes) oder das Verhalten der Anwendung auf andere Weise ändern (z. B. durch Überschreiben der gemeinsamen Einstellungen oder anderer Konfigurationsdateien der Anwendung).

Maßnahmen zur Risikominderung

Nutzereingaben nicht vertrauen

Wenn Sie Dateisystemaufrufe verwenden, sollten Sie möglichst ohne Nutzereingabe arbeiten. Generieren Sie dazu einen eindeutigen Dateinamen, wenn Sie die empfangene Datei in den Speicher schreiben.

Mit anderen Worten: Wenn die Clientanwendung die empfangene Datei in den Speicher schreibt, sollte sie den von der Serveranwendung bereitgestellten Dateinamen ignorieren und stattdessen die eigene, intern generierte eindeutige Kennung als Dateinamen verwenden.

Dieses Beispiel basiert auf dem Code unter 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;
}

Bereitgestellte Dateinamen bereinigen

Bereinigen Sie den angegebenen Dateinamen, wenn Sie die empfangene Datei in den Speicher schreiben.

Diese Maßnahme ist weniger wünschenswert als die vorherige, da es schwierig sein kann, alle potenziellen Fälle zu berücksichtigen. Dennoch: Wenn das Generieren eines eindeutigen Dateinamens nicht praktikabel ist, sollte die Clientanwendung den angegebenen Dateinamen bereinigen. Die Bereinigung umfasst:

  • Bereinigen von Pfaddurchlaufzeichen im Dateinamen
  • Kanonisierung durchführen, um zu bestätigen, dass keine Pfadüberschreitungen vorliegen

Dieser Beispielcode basiert auf der Anleitung zum Abrufen von Dateiinformationen:

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
}

Beitragende: Dimitrios Valsamaras und Michael Peck von Microsoft Threat Intelligence

Ressourcen