اعتماد نامناسب به نام فایل ارائه شده توسط ContentProvider

دسته OWASP: MASVS-CODE: کیفیت کد

نمای کلی

FileProvider ، زیر کلاس ContentProvider ، در نظر گرفته شده است تا روشی امن برای یک برنامه کاربردی ("برنامه سرور") برای اشتراک گذاری فایل ها با برنامه دیگر ("برنامه مشتری") ارائه دهد. با این حال، اگر برنامه سرویس گیرنده به درستی نام فایل ارائه شده توسط برنامه سرور را مدیریت نکند، یک برنامه سرور تحت کنترل مهاجم ممکن است بتواند FileProvider مخرب خود را برای بازنویسی فایل‌ها در حافظه برنامه ویژه برنامه کلاینت پیاده‌سازی کند.

تاثیر

اگر مهاجم بتواند فایل‌های یک برنامه را بازنویسی کند، این می‌تواند منجر به اجرای کد مخرب شود (با بازنویسی کد برنامه)، یا اجازه دهد رفتار برنامه را تغییر دهد (مثلاً با بازنویسی تنظیمات برگزیده مشترک برنامه یا سایر فایل‌های پیکربندی).

اقدامات کاهشی

به ورودی کاربر اعتماد نکنید

هنگام استفاده از تماس های سیستم فایل، با ایجاد یک نام فایل منحصر به فرد هنگام نوشتن فایل دریافتی در حافظه، ترجیح دهید بدون ورودی کاربر کار کنید.

به عبارت دیگر: وقتی برنامه مشتری فایل دریافتی را در فضای ذخیره سازی می نویسد، باید نام فایل ارائه شده توسط برنامه سرور را نادیده بگیرد و در عوض از شناسه منحصر به فرد تولید شده داخلی خود به عنوان نام فایل استفاده کند.

این مثال بر اساس کد موجود در https://developer.android.com/training/secure-file-sharing/request-file است:

کاتلین

// 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
}

جاوا

// 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;
}

نام فایل های ارائه شده را پاکسازی کنید

هنگام نوشتن فایل دریافتی در ذخیره سازی، نام فایل ارائه شده را پاکسازی کنید.

این کاهش نسبت به کاهش قبلی مطلوبیت کمتری دارد زیرا رسیدگی به تمام موارد بالقوه می تواند چالش برانگیز باشد. با این وجود: اگر ایجاد یک نام فایل منحصر به فرد عملی نیست، برنامه مشتری باید نام فایل ارائه شده را پاکسازی کند. پاکسازی شامل:

  • پاکسازی کاراکترهای پیمایش مسیر در نام فایل
  • انجام یک متعارف برای تأیید اینکه هیچ پیمایش مسیری وجود ندارد

این کد مثال بر اساس راهنمایی در مورد بازیابی اطلاعات فایل است:

کاتلین

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
}

جاوا

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

منابع