الوثوق بشكل غير صحيح في اسم الملف المقدَّم من 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;
}
تنظيف أسماء الملفات المقدَّمة
تنظيف اسم الملف المقدَّم عند كتابة الملف المستلَم في مساحة التخزين
هذا الإجراء أقل فعالية من الإجراء السابق لأنّه قد يكون من الصعب التعامل مع جميع الحالات المحتملة. ومع ذلك، إذا لم يكن من العملي إنشاء اسم ملف فريد، يجب أن يزيل تطبيق العميل أي بيانات غير صالحة من اسم الملف المقدَّم. تشمل عملية التنظيف ما يلي:
- تنظيف أحرف مسح المسار في اسم الملف
- إجراء عملية تحويل إلى تنسيق أساسي للتأكّد من عدم وجود عمليات اجتياز للمسار
يستند مثال الرمز البرمجي هذا إلى الإرشادات المتعلّقة باسترداد معلومات الملف:
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
المراجع
يخضع كل من المحتوى وعيّنات التعليمات البرمجية في هذه الصفحة للتراخيص الموضحّة في ترخيص استخدام المحتوى. إنّ Java وOpenJDK هما علامتان تجاريتان مسجَّلتان لشركة Oracle و/أو الشركات التابعة لها.
تاريخ التعديل الأخير: 2025-07-27 (حسب التوقيت العالمي المتفَّق عليه)
[[["يسهُل فهم المحتوى.","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 (حسب التوقيت العالمي المتفَّق عليه)"],[],[],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)"]]