错误地信任 ContentProvider 提供的文件名

OWASP 类别: MASVS-CODE:代码质量

概述

FileProviderContentProvider 的子类,旨在为应用(“服务器应用”)提供一种安全方法,用于 与另一个应用共享文件(“客户端应用”)。但是,如果客户端应用未正确处理服务器应用提供的文件名,则攻击者控制的服务器应用可能会实现其自己的恶意 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
}

贡献者:微软威胁情报的 Dimitrios Valsamaras 和 Michael Peck

资源