WebView – Tải URI không an toàn

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

Tổng quan

Quá trình tải URI không an toàn xảy ra khi ứng dụng Android không đánh giá được chính xác tính hợp lệ của URI trước khi tải vào WebView.

Nguyên nhân cơ bản dẫn đến loại lỗ hổng bảo mật này là do URI bao gồm nhiều phần, trong đó tối thiểu là giao thức và máy chủ lưu trữ (của phần có thẩm quyền) phải được xác minh (ví dụ: nằm trong danh sách cho phép) trước khi URI được tải vào WebView hoặc được ứng dụng dùng nội bộ.

Sau đây là các lỗi phổ biến nhất:

  • Kiểm tra máy chủ lưu trữ nhưng không kiểm tra giao thức nên kẻ tấn công có thể sử dụng các giao thức như http://, content:// hoặc javascript:// với máy chủ lưu trữ đã được xác thực.
  • Không phân tích chính xác cú pháp URI, đặc biệt là trong trường hợp nhận URI dưới dạng một chuỗi.
  • Xác thực giao thức nhưng không xác thực máy chủ (không xác thực máy chủ một cách đầy đủ).

Đối với trường hợp cuối cùng, điều này thường xảy ra khi ứng dụng cần cho phép các miền con tuỳ ý của miền chính. Vì vậy, ngay cả khi tên máy chủ đã được trích xuất chính xác, ứng dụng vẫn áp dụng các phương thức như startsWith, endsWith, hoặc contains của lớp java.lang.String để xác thực sự hiện diện của miền chính trong phần chuỗi đã trích xuất. Nếu bị dùng sai cách, các phương thức này có thể dẫn đến kết quả lỗi và buộc ứng dụng phải tin tưởng không hợp lý vào máy chủ lưu trữ có khả năng gây hại.

Mức độ tác động

Tuỳ thuộc vào bối cảnh sử dụng máy chủ lưu trữ, mức độ tác động có thể khác nhau. Trong một số trường hợp, việc tải URI độc hại (ví dụ: bỏ qua việc lọc/danh sách cho phép) trong WebView có thể dẫn đến hành vi chiếm đoạt tài khoản (ví dụ: hành vi lừa đảo), thực thi mã (ví dụ: tải JavaScript độc hại) hoặc xâm phạm thiết bị (mã khai thác được phân phối bằng siêu liên kết).

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

Khi xử lý URI chuỗi, bạn cần phân tích cú pháp chuỗi dưới dạng URI và xác thực cả giao thức và máy chủ lưu trữ:

Kotlin

fun isUriTrusted(incomingUri: String, trustedHostName: String): Boolean {
    try {
        val uri = Uri.parse(incomingUri)
        return uri.scheme == "https" && uri.host == trustedHostName
    } catch (e: NullPointerException) {
        throw NullPointerException("incomingUri is null or not well-formed")
    }
}

Java

public static boolean isUriTrusted(String incomingUri, String trustedHostName)
    throws NullPointerException {
        try {
            Uri uri = Uri.parse(incomingUri);
            return uri.getScheme().equals("https") &&
            uri.getHost().equals(trustedHostName);
        } catch (NullPointerException e) {
            throw new NullPointerException(
                "incomingUri is null or not well-formed");
        }
    }

Để xác thực máy chủ lưu trữ, sau khi tách riêng phần URI tương ứng, bạn phải xác thực toàn bộ (thay vì một phần) để xác định chính xác xem máy chủ có đáng tin cậy hay không. Khi không thể tránh sử dụng các phương thức như startsWith hoặc endsWith, bạn cần dùng đúng cú pháp và không bỏ qua các ký tự hoặc ký hiệu cần thiết (ví dụ: endsWith yêu cầu ký tự dấu chấm "." trước tên miền để có kết quả khớp chính xác). Việc bỏ qua những ký tự này có thể dẫn đến các kết quả trùng khớp không chính xác và ảnh hưởng đến tính bảo mật. Vì miền con có thể lồng ghép vô hạn, không nên dùng tính năng so khớp biểu thức chính quy để xác thực tên máy chủ.

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

Tài nguyên