WebView – 原生桥接

OWASP 类别: MASVS-PLATFORM:平台交互

概述

原生桥接(有时也称为 JavaScript 桥接)是一种机制,用于促进 WebView 与原生 Android 代码之间的通信,通过使用 addJavascriptInterface 方法实现。这允许在 WebView 中运行的 JavaScript 代码与 Android 应用的 Java 代码之间进行双向通信。 addJavascriptInterface 方法将 Java 对象公开给 WebView 的所有框架,并且任何框架都可以访问对象名称并在其上调用方法。但是,应用无法验证 WebView 内调用框架的来源,这引发了安全问题,因为内容的可信度仍然是不确定的。

原生桥接也可以使用 HTML 消息通道与 Android 的 WebViewCompat.postWebMessageWebMessagePort.postMessage 通信,以与 JavaScript Window.postMessage 通信。 WebViewCompat.postWebMessageWebMessagePort.postMessage 可以接受通过 Window.postMessage 发送的 JavaScript 消息,这些消息将在 WebView 内执行。

原生桥接存在多种风险

  • 基于 JavascriptInterface 的桥接
    • addJavascriptInterface 方法将提供的 Java 对象注入到 WebView 的每个框架中,包括 iframe,这意味着它容易受到恶意第三方将框架注入合法网站的攻击。面向 API 级别 16 或更低版本的应用尤其容易受到攻击,因为此方法可用于允许 JavaScript 控制主机应用。
    • 在启用原生桥接的 WebView 中反映不受信任的用户提供的内容会导致跨站点脚本 (XSS) 攻击。
  • 基于 MessageChannel 的桥接
    • 消息通道端点缺乏来源检查意味着将接受来自任何发送者的消息,包括包含恶意代码的消息。
    • 可能会意外地将 Java 暴露给任意 JavaScript。

影响

恶意行为者可以利用 addJavascriptInterfacepostWebMessagepostMessage 方法来访问、操作或注入他们控制的代码到 WebView 中。这可能导致用户被重定向到恶意网站、加载恶意内容或在其设备上运行恶意代码,这些代码可以提取敏感数据或提升权限。

风险:addJavascriptInterface 风险

WebView 实现浏览器的基本功能,例如页面渲染、导航和 JavaScript 执行。WebView 可在应用内部使用,以将网络内容显示为活动布局的一部分。在 WebView 中使用 addJavascriptInterface 方法实现原生桥接可能会产生安全问题,例如跨站点脚本 (XSS),或允许攻击者通过接口注入加载不受信任的内容并以意外的方式操纵主机应用,使用主机应用的权限执行 Java 代码。

缓解措施

禁用 JavaScript

在WebView不需要JavaScript的情况下,不要在setJavaScriptEnabled中调用WebSettings(例如,在显示静态HTML内容时)。默认情况下,WebView中禁用了JavaScript执行。

加载不受信任的内容时移除JavaScript接口

确保在WebView加载不受信任的内容之前,通过调用removeJavascriptInterface移除JavaScript接口中的对象。例如,这可以在调用shouldInterceptRequest时完成。

Kotlin

webView.removeJavascriptInterface("myObject")

Java

webView.removeJavascriptInterface("myObject");

仅通过HTTPS加载网页内容

如果您需要加载不受信任的内容,请确保WebView通过加密连接加载网页内容(另请参阅我们关于明文通信的指南)。通过在AndroidManifest文件中将android:usesCleartextTraffic设置为false或在网络安全配置中禁止HTTP流量来防止在未加密的连接上执行初始页面加载。有关更多信息,请参阅usesCleartextTraffic文档。

Xml

<application
    android:usesCleartextTraffic="false">
    <!-- Other application elements -->
</application>

为了确保重定向和进一步的应用浏览不会在未加密的流量上发生,请在loadUrlshouldInterceptRequest中检查HTTP方案。

Kotlin

fun loadSecureUrl(webView: WebView?, url: String?) {
    webView?.let { wv ->  // Ensure valid WebView and URL
        url?.let {
            try {
                val uri = URI(url)
                if (uri.scheme.equals("https", ignoreCase = true)) { // Enforce HTTPS scheme for security
                    wv.loadUrl(url)
                } else {
                    // Log an error or handle the case where the URL is not secure
                    System.err.println("Attempted to load a non-HTTPS URL: $url")
                }
            } catch (e: Exception) {
                // Handle exception for improper URL format
                System.err.println("Invalid URL syntax: $url")
            }
        }
    }
}

Java

public void loadSecureUrl(WebView webView, String url) {
    if (webView != null && url != null) { // Ensure valid WebView and URL
        try {
            URI uri = new URI(url);
            String scheme = uri.getScheme();
            if ("https".equalsIgnoreCase(scheme)) { // Enforce HTTPS scheme for security
                webView.loadUrl(url);
            } else {
                // Log an error or handle the case where the URL is not secure
                System.err.println("Attempted to load a non-HTTPS URL: " + url);
            }
        } catch (URISyntaxException e) {
            // Handle exception for improper URL format
            System.err.println("Invalid URL syntax: " + url);
        }
    }
}

验证不受信任的内容

如果在WebView中加载了任何外部链接,请验证方案和主机(允许列表域名)。任何不在允许列表中的域名都应由默认浏览器打开。

不要加载不受信任的内容

如果可能,只将严格限定范围的URL和应用开发者拥有的内容加载到WebView中。

不要公开敏感数据

如果您的应用使用WebView访问敏感数据,请考虑使用clearCache方法删除本地存储的任何文件,然后再使用JavaScript接口。您还可以使用服务器端标头(例如no-store)来指示应用不应缓存特定内容。

不要公开敏感功能

如果您的应用需要敏感权限或收集敏感数据,请确保它是在应用内的代码中调用的,并且向用户提供了明显的披露。避免使用JavaScript接口执行任何敏感操作或处理用户数据。

目标API级别为21或更高

使用addJavascriptInterface方法的一种安全方式是将目标API级别设置为21或更高,方法是确保该方法仅在运行API级别21或更高版本时才被调用。在API 21之前,JavaScript可以使用反射访问注入对象的公共字段。


风险:MessageChannel风险

postWebMessage()postMessage()中缺乏来源控制可能允许攻击者拦截消息或向本地处理程序发送消息。

缓解措施

在设置postWebMessage()postMessage()时,只允许来自受信任域的消息,方法是避免使用*作为目标来源,而是显式指定预期的发送域。


资源