OWASP 類別: MASVS-CODE:程式碼品質
總覽
FileProvider 是 ContentProvider 的子類別,目的是為應用程式 (「伺服器應用程式」) 提供安全的方法,與其他應用程式分享檔案 (「用戶端應用程式」)。不過,如果用戶端應用程式未正確處理伺服器應用程式提供的檔案名稱,攻擊者控制的伺服器應用程式可能就能實作自己的惡意 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
資源
- Dirty Stream Attack:將 Android 分享目標變成攻擊媒介
- 安全檔案共用
- 要求共用檔案文件
- Retrieve Info
- FileProvider
- 路徑遍歷
- CWE-73 檔案名稱或路徑的外部控制