การเชื่อถือชื่อไฟล์ที่ได้จาก ContentProvider อย่างไม่ถูกต้อง
จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน
บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ
หมวดหมู่ OWASP: MASVS-CODE: คุณภาพของโค้ด
ภาพรวม
FileProvider ซึ่งเป็นคลาสย่อยของ ContentProvider มีไว้เพื่อ
จัดหาวิธีที่ปลอดภัยสำหรับแอปพลิเคชัน ("แอปพลิเคชันเซิร์ฟเวอร์") ในการแชร์
ไฟล์กับแอปพลิเคชันอื่น ("แอปพลิเคชันไคลเอ็นต์") อย่างไรก็ตาม หาก
แอปพลิเคชันไคลเอ็นต์จัดการชื่อไฟล์ที่แอปพลิเคชันเซิร์ฟเวอร์ระบุอย่างไม่ถูกต้อง
แอปพลิเคชันเซิร์ฟเวอร์ที่ผู้โจมตีควบคุมอาจสามารถใช้FileProvider ที่เป็นอันตรายของตนเอง
เพื่อเขียนทับไฟล์ในที่เก็บข้อมูลเฉพาะของแอปในแอปพลิเคชันไคลเอ็นต์
ผลกระทบ
หากผู้โจมตีเขียนทับไฟล์ของแอปพลิเคชันได้ ก็อาจนำไปสู่การเรียกใช้โค้ดที่เป็นอันตราย (โดยการเขียนทับโค้ดของแอปพลิเคชัน) หรืออนุญาตให้แก้ไขลักษณะการทำงานของแอปพลิเคชัน (เช่น โดยการเขียนทับค่ากำหนดที่แชร์ของแอปพลิเคชันหรือไฟล์การกำหนดค่าอื่นๆ)
การลดปัญหา
ควรทำงานโดยไม่ต้องป้อนข้อมูลจากผู้ใช้เมื่อใช้การเรียกไฟล์ระบบโดยการสร้างชื่อไฟล์ที่ไม่ซ้ำกันเมื่อเขียนไฟล์ที่ได้รับไปยังพื้นที่เก็บข้อมูล
กล่าวคือ เมื่อแอปพลิเคชันไคลเอ็นต์เขียนไฟล์ที่ได้รับไปยังที่เก็บข้อมูล
แอปพลิเคชันควรไม่สนใจชื่อไฟล์ที่แอปพลิเคชันเซิร์ฟเวอร์ระบุ และใช้
ตัวระบุที่ไม่ซ้ำที่สร้างขึ้นภายในของตัวเองเป็นชื่อไฟล์แทน
ตัวอย่างนี้สร้างขึ้นจากโค้ดที่พบใน
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;
}
ล้างข้อมูลชื่อไฟล์ที่ระบุ
ล้างข้อมูลชื่อไฟล์ที่ระบุเมื่อเขียนไฟล์ที่ได้รับไปยังที่เก็บข้อมูล
การลดความเสี่ยงนี้ไม่เป็นที่ต้องการเท่าการลดความเสี่ยงก่อนหน้าเนื่องจากอาจ
จัดการกรณีที่อาจเกิดขึ้นทั้งหมดได้ยาก อย่างไรก็ตาม หากการสร้างชื่อไฟล์ที่ไม่ซ้ำกันไม่สะดวก แอปพลิเคชันไคลเอ็นต์ควรล้างชื่อไฟล์ที่ระบุ การล้างข้อมูลรวมถึง
- การล้างอักขระของ Path Traversal ในชื่อไฟล์
- ดำเนินการ Canonicalization เพื่อยืนยันว่าไม่มีการข้ามเส้นทาง
โค้ดตัวอย่างนี้สร้างขึ้นตามคำแนะนำเกี่ยวกับการดึงข้อมูลไฟล์
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
}
ผู้ร่วมให้ข้อมูล: Dimitrios Valsamaras และ Michael Peck จาก Microsoft Threat
Intelligence
แหล่งข้อมูล
ตัวอย่างเนื้อหาและโค้ดในหน้าเว็บนี้ขึ้นอยู่กับใบอนุญาตที่อธิบายไว้ในใบอนุญาตการใช้เนื้อหา Java และ OpenJDK เป็นเครื่องหมายการค้าหรือเครื่องหมายการค้าจดทะเบียนของ Oracle และ/หรือบริษัทในเครือ
อัปเดตล่าสุด 2025-07-27 UTC
[[["เข้าใจง่าย","easyToUnderstand","thumb-up"],["แก้ปัญหาของฉันได้","solvedMyProblem","thumb-up"],["อื่นๆ","otherUp","thumb-up"]],[["ไม่มีข้อมูลที่ฉันต้องการ","missingTheInformationINeed","thumb-down"],["ซับซ้อนเกินไป/มีหลายขั้นตอนมากเกินไป","tooComplicatedTooManySteps","thumb-down"],["ล้าสมัย","outOfDate","thumb-down"],["ปัญหาเกี่ยวกับการแปล","translationIssue","thumb-down"],["ตัวอย่าง/ปัญหาเกี่ยวกับโค้ด","samplesCodeIssue","thumb-down"],["อื่นๆ","otherDown","thumb-down"]],["อัปเดตล่าสุด 2025-07-27 UTC"],[],[],null,["# Improperly trusting ContentProvider-provided filename\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-CODE: Code Quality](https://mas.owasp.org/MASVS/10-MASVS-CODE)\n\n\nOverview\n--------\n\n[*FileProvider*](/reference/androidx/core/content/FileProvider), a subclass of [*ContentProvider*](/reference/android/content/ContentProvider), is intended to\nprovide a secure method for an application (\"server application\") to [share\nfiles with another application](/training/secure-file-sharing) (\"client application\"). However, if the\nclient application does not properly handle the filename provided by the server\napplication, an attacker-controlled server application may be able to implement\nits own malicious *FileProvider* to overwrite files in the client application's\napp-specific storage.\n\nImpact\n------\n\nIf an attacker can overwrite an application's files, this can lead to malicious\ncode execution (by overwriting the application's code), or allow otherwise\nmodifying the application's behavior (for example, by overwriting the\napplication's shared preferences or other configuration files).\n\nMitigations\n-----------\n\n### Don't Trust User Input\n\nPrefer working without user input when using file system calls by generating a\nunique filename when writing the received file to storage.\n\nIn other words: When the client application writes the received file to storage,\nit should ignore the filename provided by the server application and instead use\nits own internally generated unique identifier as the filename.\n\nThis example builds upon the code found at\n[https://developer.android.com/training/secure-file-sharing/request-file](/training/secure-file-sharing/request-file#java): \n\n### Kotlin\n\n // Code in\n // https://developer.android.com/training/secure-file-sharing/request-file#OpenFile\n // used to obtain file descriptor (fd)\n\n try {\n val inputStream = FileInputStream(fd)\n val tempFile = File.createTempFile(\"temp\", null, cacheDir)\n val outputStream = FileOutputStream(tempFile)\n val buf = ByteArray(1024)\n var len: Int\n len = inputStream.read(buf)\n while (len \u003e 0) {\n if (len != -1) {\n outputStream.write(buf, 0, len)\n len = inputStream.read(buf)\n }\n }\n inputStream.close()\n outputStream.close()\n } catch (e: IOException) {\n e.printStackTrace()\n Log.e(\"MainActivity\", \"File copy error.\")\n return\n }\n\n### Java\n\n // Code in\n // https://developer.android.com/training/secure-file-sharing/request-file#OpenFile\n // used to obtain file descriptor (fd)\n\n FileInputStream inputStream = new FileInputStream(fd);\n\n // Create a temporary file\n File tempFile = File.createTempFile(\"temp\", null, getCacheDir());\n\n // Copy the contents of the file to the temporary file\n try {\n OutputStream outputStream = new FileOutputStream(tempFile))\n byte[] buffer = new byte[1024];\n int length;\n while ((length = inputStream.read(buffer)) \u003e 0) {\n outputStream.write(buffer, 0, length);\n }\n } catch (IOException e) {\n e.printStackTrace();\n Log.e(\"MainActivity\", \"File copy error.\");\n return;\n }\n\n### Sanitize Provided Filenames\n\nSanitize the provided filename when writing the received file to storage.\n\nThis mitigation is less desirable than the preceding mitigation because it can\nbe challenging to handle all potential cases. Nonetheless: If generating a\nunique filename is not practical, the client application should sanitize the\nprovided filename. Sanitization includes:\n\n- Sanitizing path traversal characters in the filename\n- Performing a canonicalization to confirm there are no path traversals\n\nThis example code builds upon the guidance on [retrieving file information](/training/secure-file-sharing/retrieve-info): \n\n### Kotlin\n\n protected fun sanitizeFilename(displayName: String): String {\n val badCharacters = arrayOf(\"..\", \"/\")\n val segments = displayName.split(\"/\")\n var fileName = segments[segments.size - 1]\n for (suspString in badCharacters) {\n fileName = fileName.replace(suspString, \"_\")\n }\n return fileName\n }\n\n val displayName = returnCursor.getString(nameIndex)\n val fileName = sanitizeFilename(displayName)\n val filePath = File(context.filesDir, fileName).path\n\n // saferOpenFile defined in Android developer documentation\n val outputFile = saferOpenFile(filePath, context.filesDir.canonicalPath)\n\n // fd obtained using Requesting a shared file from Android developer\n // documentation\n\n val inputStream = FileInputStream(fd)\n\n // Copy the contents of the file to the new file\n try {\n val outputStream = FileOutputStream(outputFile)\n val buffer = ByteArray(1024)\n var length: Int\n while (inputStream.read(buffer).also { length = it } \u003e 0) {\n outputStream.write(buffer, 0, length)\n }\n } catch (e: IOException) {\n // Handle exception\n }\n\n### Java\n\n protected String sanitizeFilename(String displayName) {\n String[] badCharacters = new String[] { \"..\", \"/\" };\n String[] segments = displayName.split(\"/\");\n String fileName = segments[segments.length - 1];\n for (String suspString : badCharacters) {\n fileName = fileName.replace(suspString, \"_\");\n }\n return fileName;\n }\n\n String displayName = returnCursor.getString(nameIndex);\n String fileName = sanitizeFilename(displayName);\n String filePath = new File(context.getFilesDir(), fileName).getPath();\n\n // saferOpenFile defined in Android developer documentation\n\n File outputFile = saferOpenFile(filePath,\n context.getFilesDir().getCanonicalPath());\n\n // fd obtained using Requesting a shared file from Android developer\n // documentation\n\n FileInputStream inputStream = new FileInputStream(fd);\n\n // Copy the contents of the file to the new file\n try {\n OutputStream outputStream = new FileOutputStream(outputFile))\n byte[] buffer = new byte[1024];\n int length;\n while ((length = inputStream.read(buffer)) \u003e 0) {\n outputStream.write(buffer, 0, length);\n }\n } catch (IOException e) {\n // Handle exception\n }\n\nContributors: Dimitrios Valsamaras and Michael Peck of Microsoft Threat\nIntelligence\n\nResources\n---------\n\n- [Dirty Stream Attack: Turning Android Share Targets Into Attack Vectors](https://i.blackhat.com/Asia-23/AS-23-Valsamaras-Dirty-Stream-Attack-Turning-Android.pdf)\n- [Secure File Sharing](/training/secure-file-sharing)\n- [Request a Shared File documentation](/training/secure-file-sharing/request-file)\n- [Retrieve Info](/training/secure-file-sharing/retrieve-info)\n- [FileProvider](/reference/androidx/core/content/FileProvider)\n- [Path Traversal](/topic/security/risks/path-traversal)\n- [CWE-73 External Control of Filename or Path](https://cwe.mitre.org/data/definitions/73)"]]