不當信任 ContentProvider 提供的檔案名稱

OWASP 類別: MASVS-CODE:程式碼品質

總覽

FileProviderContentProvider 的子類別,目的是為應用程式 (「伺服器應用程式」) 提供安全的方法,與其他應用程式分享檔案 (「用戶端應用程式」)。不過,如果用戶端應用程式未正確處理伺服器應用程式提供的檔案名稱,攻擊者控制的伺服器應用程式可能就能實作自己的惡意 FileProvider,覆寫用戶端應用程式特定於應用程式的儲存空間中的檔案。

影響

如果攻擊者可以覆寫應用程式的檔案,可能會導致惡意程式碼執行 (透過覆寫應用程式的程式碼),或允許以其他方式修改應用程式的行為 (例如覆寫應用程式的共用偏好設定或其他設定檔)。

因應措施

不要信任使用者輸入內容

使用檔案系統呼叫時,建議您在將收到的檔案寫入儲存空間時產生專屬檔案名稱,盡量避免需要使用者輸入。

換句話說,當用戶端應用程式將收到的檔案寫入儲存空間時,應忽略伺服器應用程式提供的檔案名稱,改為使用自行產生的專屬 ID 做為檔案名稱。

這個範例是以 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;
}

清除提供的檔案名稱

將收到的檔案寫入儲存空間時,請清除提供的檔案名稱。

相較於上述緩解措施,這種做法較不理想,因為處理所有可能情況可能很困難。但如果無法產生不重複的檔案名稱,用戶端應用程式應清除提供的檔案名稱。清潔程序包括:

  • 清除檔案名稱中的路徑周遊字元
  • 執行正規化,確認沒有路徑遍歷

這個範例程式碼是以擷取檔案資訊的指南為基礎:

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
}

協作者:Microsoft Threat Intelligence 的 Dimitrios Valsamaras 和 Michael Peck

資源