فئة 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 في اسم الملف
- إجراء عملية تحويل إلى تنسيق أساسي للتأكّد من عدم وجود عمليات اجتياز مسار
يستند هذا الرمز البرمجي النموذجي إلى الإرشادات المتعلّقة باسترداد معلومات الملف:
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
الموارد
- هجوم Dirty Stream: تحويل أهداف المشاركة في Android إلى متجهات هجوم
- مشاركة الملفات بأمان
- مستندات حول ميزة "ملف مشترك"
- استرداد المعلومات
- FileProvider
- ثغرة Path Traversal
- CWE-73 External Control of Filename or Path