Niewłaściwe traktowanie nazwy pliku podanej przez dostawcę ContentProvider

Kategoria OWASP: MASVS-CODE: jakość kodu

Przegląd

FileProvider, podklasa ContentProvider, ma zapewnić bezpieczną metodę udostępniania plików przez aplikację ("aplikację serwera") innej aplikacji ("aplikacji klienta"). Jeśli jednak aplikacja klienta nieprawidłowo obsługuje nazwę pliku podaną przez aplikację serwera, aplikacja serwera kontrolowana przez atakującego może zaimplementować własną złośliwą FileProvider, aby zastąpić pliki w pamięci masowej aplikacji klienta.

Wpływ

Jeśli atakujący może zastąpić pliki aplikacji, może to doprowadzić do wykonania złośliwego kodu (przez zastąpienie kodu aplikacji) lub umożliwić modyfikowanie zachowania aplikacji (na przykład przez zastąpienie preferencji udostępnionych aplikacji lub innych plików konfiguracyjnych).

Środki zaradcze

Nie ufaj danym wejściowym użytkownika

Podczas korzystania z wywołań systemu plików lepiej jest pracować bez danych wejściowych użytkownika, generując unikalną nazwę pliku podczas zapisywania otrzymanego pliku w pamięci masowej.

Innymi słowy, gdy aplikacja klienta zapisuje otrzymany plik w pamięci masowej, powinna ignorować nazwę pliku podaną przez aplikację serwera i zamiast tego używać własnego, wygenerowanego wewnętrznie unikalnego identyfikatora jako nazwy pliku.

Ten przykład opiera się na kodzie znalezionym na stronie 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;
}

Czyszczenie podanych nazw plików

Podczas zapisywania otrzymanego pliku w pamięci masowej wyczyść podaną nazwę pliku.

To rozwiązanie jest mniej korzystne niż poprzednie, ponieważ może być trudne do zastosowania we wszystkich potencjalnych przypadkach. Jeśli jednak wygenerowanie unikalnej nazwy pliku nie jest praktyczne, aplikacja klienta powinna wyczyścić podaną nazwę pliku. Czyszczenie obejmuje:

  • czyszczenie nazwy pliku ze znaków przemierzania ścieżki;
  • przeprowadzanie kanonizacji w celu potwierdzenia, że nie ma przemierzania ścieżki.

Ten przykładowy kod opiera się na wskazówkach dotyczących pobierania informacji o pliku:

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
}

Współautorzy: Dimitrios Valsamaras i Michael Peck z Microsoft Threat Intelligence

Zasoby