Trình phân giải nội dung

Danh mục OWASP: MASVS-PLATFORM: Tương tác với nền tảng

Tổng quan

Theo tài liệu này, ContentResolver là một "lớp cung cấp cho ứng dụng quyền truy cập vào mô hình nội dung". ContentResolvers cung cấp các phương thức để tương tác, tìm nạp hoặc sửa đổi nội dung được cung cấp từ:

  • Ứng dụng đã cài đặt (lược đồ URI content://)
  • Hệ thống tệp (lược đồ URI file://)
  • Các API hỗ trợ do Android cung cấp (lược đồ URI android.resource://).

Tóm lại, các lỗ hổng bảo mật liên quan đến ContentResolver thuộc về lớp đại diện bị nhầm lẫn vì kẻ tấn công có thể dùng các đặc quyền của ứng dụng dễ bị tấn công để truy cập vào nội dung được bảo vệ.

Rủi ro: Việc sử dụng sai mục đích dựa trên URI file:// không đáng tin cậy

Việc sử dụng sai mục đích ContentResolver bằng cách dùng lỗ hổng bảo mật URI file:// sẽ khai thác khả năng của ContentResolver để trả về chỉ số mô tả tệp theo URI. Lỗ hổng bảo mật này ảnh hưởng đến các chức năng như openFile(), openFileDescriptor(), openInputStream(), openOutputStream() hoặc openAssetFileDescriptor() từ APIContentResolver. Đây là lỗ hổng bảo mật có thể bị lợi dụng bằng URI file:// do kẻ tấn công kiểm soát toàn bộ hoặc một phần để buộc ứng dụng truy cập vào các tệp vốn không cho phép truy cập, chẳng hạn như cơ sở dữ liệu nội bộ hoặc các lựa chọn ưu tiên chung.

Một trong những tình huống tấn công có thể xảy ra là tạo thư viện độc hại hoặc bộ chọn tệp (khi được một ứng dụng dễ bị tấn công dùng) sẽ trả về URI độc hại.

Có một số biến thể của hình thức tấn công này:

  • URI file:// do kẻ tấn công kiểm soát toàn bộ trỏ đến các tệp nội bộ của ứng dụng
  • Một phần URI file:// nằm dưới quyền kiểm soát của kẻ tấn công, khiến nó dễ dàng bị truyền tải qua đường dẫn
  • URI file:// nhắm đến một đường liên kết tượng trưng (symlink) do kẻ tấn công kiểm soát sẽ trỏ đến các tệp nội bộ của ứng dụng
  • Tương tự như biến thể trước, nhưng ở đây, kẻ tấn công nhiều lần hoán đổi mục tiêu trong đường liên kết tượng trưng từ mục tiêu hợp lệ sang các tệp nội bộ của ứng dụng. Mục tiêu là khai thác một điều kiện tranh đấu giữa bước kiểm tra bảo mật tiềm năng và việc sử dụng đường dẫn tệp

Tác động

Mức độ tác động của việc khai thác lỗ hổng bảo mật này sẽ khác nhau tuỳ thuộc vào mục đích sử dụng của ContentResolver. Trong nhiều trường hợp, điều này có thể dẫn đến việc dữ liệu được bảo vệ của ứng dụng bị lọc bớt hoặc các bên không được phép sẽ sửa đổi dữ liệu được bảo vệ.

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

Để giảm thiểu lỗ hổng bảo mật này, hãy sử dụng thuật toán dưới đây để xác thực chỉ số mô tả tệp. Sau khi vượt qua quy trình xác thực, bạn có thể sử dụng chỉ số mô tả tệp một cách an toàn.

Kotlin

fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
    // Canonicalize to resolve symlinks and path traversals.
    val fdCanonical = File(fileUri.path!!).canonicalPath

    val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)

    // Lstat doesn't follow the symlink.
    val canonicalFileStat: StructStat = Os.lstat(fdCanonical)

    // Since we canonicalized (followed the links) the path already,
    // the path shouldn't point to symlink unless it was changed in the
    // meantime.
    if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
        return false
    }

    val sameFile =
        pfdStat.st_dev == canonicalFileStat.st_dev &&
        pfdStat.st_ino == canonicalFileStat.st_ino

    if (!sameFile) {
        return false
    }

    return !isBlockedPath(ctx, fdCanonical)
}

fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
    // Paths that should rarely be exposed
    if (fdCanonical.startsWith("/proc/") ||
        fdCanonical.startsWith("/data/misc/")) {
        return true
    }

    // Implement logic to block desired directories. For example, specify
    // the entire app data/ directory to block all access.
}

Java

boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
    // Canonicalize to resolve symlinks and path traversals
    String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();

    StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());

    // Lstat doesn't follow the symlink.
    StructStat canonicalFileStat = Os.lstat(fdCanonical);

    // Since we canonicalized (followed the links) the path already,
    // the path shouldn't point to symlink unless it was changed in the meantime
    if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
        return false;
    }

    boolean sameFile =
        pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;

    if (!sameFile) {
        return false;
    }

    return !isBlockedPath(ctx, fdCanonical);
}

boolean isBlockedPath(Context ctx, String fdCanonical) {
       
        // Paths that should rarely be exposed
        if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
            return true;
        }

        // Implement logic to block desired directories. For example, specify
        // the entire app data/ directory to block all access.
}


Rủi ro: Việc sử dụng sai mục đích dựa trên URI content:// không đáng tin cậy

Việc sử mục sai mục đích ContentResolver bằng cách dùng lỗ hổng bảo mật URI content:// sẽ xảy ra khi URI do kẻ tấn công kiểm soát hoàn toàn hoặc một phần được chuyển vào API ContentResolver để hoạt động trên nội dung không nhằm mục đích có thể truy cập được.

Có 2 tình huống chính của hình thức tấn công này:

  • Ứng dụng hoạt động trên nội dung nội bộ của riêng ứng dụng. Ví dụ: sau khi nhận được URI từ kẻ tấn công, ứng dụng thư sẽ đính kèm dữ liệu từ trình cung cấp nội dung nội bộ của chính ứng dụng thay vì ảnh bên ngoài.
  • Ứng dụng hoạt động như một proxy, sau đó giúp kẻ tấn công truy cập vào dữ liệu của một ứng dụng khác. Ví dụ: ứng dụng thư đính kèm dữ liệu từ ứng dụng X được bảo vệ bằng một quyền thường sẽ không cho phép kẻ tấn công xem các tệp đính kèm cụ thể đó. Ứng dụng này có sẵn cho ứng dụng thực hiện tệp đính kèm, nhưng ban đầu chưa chuyển tiếp nội dung này đến kẻ tấn công.

Một tình huống tấn công có thể xảy ra là tạo thư viện độc hại hoặc bộ chọn tệp (khi được một ứng dụng dễ bị tấn công dùng) sẽ trả về URI độc hại.

Tác động

Mức độ tác động của việc khai thác lỗ hổng bảo mật này sẽ khác nhau tuỳ thuộc vào ngữ cảnh liên quan đến ContentResolver. Điều này có thể dẫn đến việc dữ liệu được bảo vệ của ứng dụng bị lọc bớt hoặc các bên không được phép sẽ sửa đổi dữ liệu được bảo vệ.

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

Chung

Xác thực URI đến. Ví dụ: việc sử dụng danh sách cho phép của các cơ quan dự kiến được xem là phương pháp hay.

URI nhắm mục tiêu trình cung cấp nội dung không được xuất hoặc được bảo vệ bằng quyền thuộc về ứng dụng dễ bị tấn công

Kiểm tra xem liệu URI có nhắm mục tiêu ứng dụng của bạn hay không:

Kotlin

fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
    val authority: String = uri.authority.toString()
    val info: ProviderInfo =
        ctx.packageManager.resolveContentProvider(authority, 0)!!

    return ctx.packageName.equals(info.packageName)
}

Java

boolean belongsToCurrentApplication(Context ctx, Uri uri){
    String authority = uri.getAuthority();
    ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);

    return ctx.getPackageName().equals(info.packageName);
}

Hoặc nếu trình cung cấp được nhắm mục tiêu đã được xuất:

Kotlin

fun isExported(ctx: Context, uri: Uri): Boolean {
    val authority = uri.authority.toString()
    val info: ProviderInfo =
            ctx.packageManager.resolveContentProvider(authority, 0)!!

    return info.exported
}

Java

boolean isExported(Context ctx, Uri uri){
    String authority = uri.getAuthority();
    ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);      

    return info.exported;
}

Hoặc nếu được cấp quyền rõ ràng đối với URI – việc kiểm tra này dựa trên giả định rằng nếu được cấp quyền rõ ràng để truy cập vào dữ liệu, URI đó sẽ không độc hại:

Kotlin

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
    val pid: Int = Process.myPid()
    val uid: Int = Process.myUid()
    return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
            PackageManager.PERMISSION_GRANTED
}

Java

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
    int pid = Process.myPid();
    int uid = Process.myUid();

    return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}

URI nhắm mục tiêu đến một ContentProvider được bảo vệ quyền thuộc về một ứng dụng khác tin tưởng ứng dụng dễ bị tấn công đó.

Hình thức tấn công này có liên quan đến các tình huống sau:

  • Hệ sinh thái của ứng dụng mà trong đó ứng dụng xác định và dùng quyền tuỳ chỉnh hoặc các cơ chế xác thực khác.
  • Các cuộc tấn công proxy quyền, trong đó kẻ tấn công sử dụng sai mục đích một ứng dụng dễ bị tấn công mà ứng dụng đó đang giữ quyền để truy xuất dữ liệu từ nhà cung cấp hệ thống khi bắt đầu chạy (ví dụ: quyền READ_CONTACTS).

Kiểm tra xem quyền URI đã được cấp hay chưa:

Kotlin

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
    val pid: Int = Process.myPid()
    val uid: Int = Process.myUid()
    return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
            PackageManager.PERMISSION_GRANTED
}

Java

// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
    int pid = Process.myPid();
    int uid = Process.myUid();

    return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}

Nếu việc sử dụng của trình cung cấp nội dung khác không yêu cầu cấp quyền (chẳng hạn như khi ứng dụng cho phép mọi ứng dụng từ hệ sinh thái truy cập vào tất cả dữ liệu), thì tuyệt đối cấm sử dụng các cơ quan này.