Неправильное доверие к имени файла, предоставленному 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
}

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

Очистка предоставленных имен файлов

При записи полученного файла в хранилище необходимо проверить предоставленное имя файла на наличие ошибок.

Этот способ защиты менее предпочтителен, чем предыдущий, поскольку обработка всех потенциальных случаев может быть сложной задачей. Тем не менее: если создание уникального имени файла нецелесообразно, клиентское приложение должно проверить предоставленное имя файла на наличие уязвимостей. Проверка включает в себя:

  • Очистка символов обхода пути в имени файла
  • Выполнение канонизации для подтверждения отсутствия обходов пути.

Этот пример кода основан на рекомендациях по получению информации о файле :

Котлин

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.

Ресурсы