Webviews – 不安全的 URI 加载

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

概览

当 Android 应用未能正确评估 URI 的有效性便将其加载到 WebView 时,就会发生不安全的 URI 加载。

此类漏洞的根本原因在于,URI 由多个部分组成,其中,方案和主机(权限部分)必须在 URI 加载到 WebView 或被应用内部使用之前得到验证(例如,加入白名单)。

最常见的错误包括

  • 仅检查主机但不检查方案,导致攻击者可以使用 http://content://javascript:// 等方案访问已验证的主机。
  • 未能正确解析 URI,尤其是在 URI 作为字符串接收的情况下。
  • 验证方案但主机验证不足。

关于最后一种情况,这通常发生在应用需要允许主要域的任意子域时。因此,即使已正确提取主机名,应用仍使用 java.lang.String 类的 startsWithendsWithcontains 等方法来验证提取的字符串部分中是否存在主要域。如果使用不当,这些方法可能导致错误的结果,并迫使应用不当地信任可能恶意的主机。

影响

根据主机的使用环境,影响可能会有所不同。在将恶意 URI(即绕过过滤/白名单的 URI)加载到 WebView 的情况下,可能导致账户被盗(例如,使用网络钓鱼)、代码执行(例如,加载恶意 JavaScript)或设备被入侵(通过超链接传递利用代码)。

缓解措施

处理字符串 URI 时,将其解析为 URI 并同时验证方案和主机非常重要

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

对于主机验证,在隔离相应的 URI 部分后,重要的是要对其进行完整(而非部分)验证,以便准确判断主机是否受信任。当无法避免使用 startsWithendsWith 等方法时,务必使用正确的语法,不要忽略必要的字符或符号(例如,endsWith 需要在域名之前包含“.”点字符才能准确匹配)。忽略这些字符可能导致不准确的匹配并损害安全性。由于子域可以无限嵌套,因此不建议使用正则表达式匹配作为验证主机名的策略。

贡献者:Microsoft 威胁情报部的 Dimitrios Valsamaras 和 Michael Peck

资源