Terlalu memercayai nama file yang disediakan ContentProvider

Kategori OWASP: MASVS-CODE: Kualitas Kode

Ringkasan

FileProvider, subkelas ContentProvider, dimaksudkan untuk menyediakan metode yang aman bagi aplikasi ("aplikasi server") untuk membagikan file dengan aplikasi lain ("aplikasi klien"). Namun, jika aplikasi klien tidak menangani nama file yang diberikan oleh aplikasi server dengan benar, aplikasi server yang dikontrol oleh penyerang mungkin dapat menerapkan FileProvider berbahaya miliknya sendiri untuk menimpa file di penyimpanan khusus aplikasi milik aplikasi klien.

Dampak

Jika penyerang dapat mengganti file aplikasi, hal ini dapat menyebabkan eksekusi kode berbahaya (dengan mengganti kode aplikasi), atau memungkinkan modifikasi perilaku aplikasi (misalnya, dengan mengganti preferensi bersama aplikasi atau file konfigurasi lainnya).

Mitigasi

Jangan Percayai Input Pengguna

Lebih baik bekerja tanpa input pengguna saat menggunakan panggilan sistem file dengan membuat nama file unik saat menulis file yang diterima ke penyimpanan.

Dengan kata lain: Saat aplikasi klien menulis file yang diterima ke penyimpanan, aplikasi tersebut harus mengabaikan nama file yang diberikan oleh aplikasi server dan menggunakan ID unik yang dibuat secara internal sebagai nama file.

Contoh ini dibuat berdasarkan kode yang ditemukan di 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;
}

Membersihkan Nama File yang Disediakan

Membersihkan nama file yang diberikan saat menulis file yang diterima ke penyimpanan.

Mitigasi ini kurang diinginkan daripada mitigasi sebelumnya karena dapat menjadi tantangan untuk menangani semua kemungkinan kasus. Namun: Jika membuat nama file unik tidak praktis, aplikasi klien harus membersihkan nama file yang diberikan. Sanitasi mencakup:

  • Membersihkan karakter path traversal dalam nama file
  • Melakukan kanonisasi untuk mengonfirmasi bahwa tidak ada penjelajahan jalur

Contoh kode ini dibuat berdasarkan panduan tentang mengambil informasi 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
}

Kontributor: Dimitrios Valsamaras dan Michael Peck dari Microsoft Threat Intelligence

Referensi