اعتماد نامناسب به نام فایل ارائه شده توسط 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
}

مشارکت‌کنندگان: دیمیتریوس والساماراس و مایکل پک از بخش اطلاعات تهدید مایکروسافت

منابع