Tin tưởng không đúng cách vào tên tệp do ContentProvider cung cấp

Danh mục OWASP: MASVS-CODE: Chất lượng mã

Tổng quan

FileProvider là một lớp con của ContentProvider nhằm cung cấp phương thức an toàn cho một ứng dụng ("ứng dụng máy chủ") để chia sẻ tệp với một ứng dụng khác ("ứng dụng khách"). Tuy nhiên, nếu ứng dụng khách không xử lý đúng tên tệp do ứng dụng máy chủ cung cấp, thì ứng dụng máy chủ do kẻ tấn công kiểm soát có thể triển khai FileProvider độc hại của riêng ứng dụng đó để ghi đè các tệp trong bộ nhớ dành riêng cho ứng dụng của ứng dụng khách.

Mức độ tác động

Nếu kẻ tấn công có thể ghi đè các tệp của ứng dụng, thì điều này có thể dẫn đến việc thực thi mã độc hại (bằng cách ghi đè mã của ứng dụng) hoặc cho phép sửa đổi hành vi của ứng dụng (ví dụ: ghi đè các lựa chọn ưu tiên chung của ứng dụng hoặc các tệp cấu hình khác).

Giải pháp giảm thiểu

Không tin tưởng hoạt động đầu vào của người dùng

Ưu tiên làm việc mà không cần hoạt động đầu vào của người dùng khi sử dụng các lệnh gọi hệ thống tệp bằng cách tạo tên tệp duy nhất khi ghi tệp đã nhận vào bộ nhớ.

Nói cách khác: Khi ghi tệp đã nhận vào bộ nhớ, ứng dụng khách nên bỏ qua tên tệp do ứng dụng máy chủ cung cấp và thay vào đó, sử dụng giá trị nhận dạng duy nhất được tạo nội bộ của riêng ứng dụng đó làm tên tệp.

Ví dụ này dựa trên mã có tại 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;
}

Dọn dẹp tên tệp được cung cấp

Dọn dẹp tên tệp được cung cấp khi ghi tệp đã nhận vào bộ nhớ.

Giải pháp giảm thiểu này ít được mong đợi hơn so với giải pháp giảm thiểu trước đó vì có thể khó xử lý tất cả các trường hợp tiềm ẩn. Tuy nhiên: Nếu việc tạo tên tệp duy nhất không khả thi, thì ứng dụng khách cần dọn dẹp tên tệp đã cung cấp. Hoạt động dọn dẹp bao gồm:

  • Dọn dẹp các ký tự truyền tải qua đường dẫn trong tên tệp
  • Chuẩn hoá để xác nhận rằng không có hoạt động truyền tải qua đường dẫn nào

Mã ví dụ này dựa trên hướng dẫn về cách truy xuất thông tin tệp:

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
}

Cộng tác viên: Dimitrios Valsamaras và Michael Peck của Microsoft Threat Intelligence

Tài nguyên