WebView – Native bridges

OWASP category: MASVS-PLATFORM: Platform Interaction

Overview

A native bridge, sometimes known as a JavaScript bridge, is a mechanism that facilitates communication between a WebView and native Android code, achieved by using the addJavascriptInterface method. This allows for two-way communication between JavaScript code running in the WebView and the Android application's Java code. The addJavascriptInterface method exposes a Java object to all of a WebView's frames, and any frame can access the object name and call methods on it. However, there is no mechanism for the application to verify the origin of the calling frame within the WebView, which raises security concerns as the trustworthiness of the content remains indeterminate.

A native bridge can also be implemented with HTML message channels using Android's WebViewCompat.postWebMessage or WebMessagePort.postMessage to communicate with the JavaScript Window.postMessage. WebViewCompat.postWebMessage and WebMessagePort.postMessage can accept JavaScript messages sent through Window.postMessage which will be executed within the WebView.

There are multiple risks associated with native bridges:

  • JavascriptInterface-based bridges:
    • The addJavascriptInterface method injects a supplied Java object into every frame of the WebView, including iframes, which means it is susceptible to attack by malicious third parties injecting frames into a legitimate website. Applications targeting API level 16 or earlier are particularly at risk of attack because this method can be used to allow JavaScript to control the host application.
    • Reflecting untrusted user-provided content in native bridge-enabled WebViews allows for cross-site scripting (XSS) attacks.
  • MessageChannel-based bridges:
    • Lack of origin checks on message channel endpoints means that messages will be accepted from any sender, including those containing malicious code.
    • It is possible to accidentally expose Java to arbitrary JavaScript.

Impact

The addJavascriptInterface, postWebMessage, and postMessage methods can be leveraged by malicious actors to access, manipulate, or inject code they control into a WebView. This may lead to users being redirected to malicious sites, loading malicious content, or having malicious code run on their devices that can extract sensitive data or achieve privilege escalation.

Risk: addJavascriptInterface risks

WebView implements basic functionalities of a browser, such as page rendering, navigation, and JavaScript execution. WebView can be used inside an application to display web content as part of an activity layout. Implementing a native bridge within a WebView using the addJavascriptInterface method can create security issues such as cross-site scripting (XSS), or allow attackers to load untrusted content through interface injection and manipulate the host application in unintended ways, executing Java code with the permissions of the host application.

Mitigations

Disable JavaScript

In scenarios where WebView doesn't require JavaScript, don't call setJavaScriptEnabled within WebSettings (for example, while displaying static HTML content). By default, JavaScript execution is disabled in WebView.

Remove JavaScript interface when loading untrusted content

Ensure objects from the JavaScript interface are removed by calling removeJavascriptInterface before untrusted content is loaded by the WebView. For example, this can be done in a call to shouldInterceptRequest.

Kotlin

webView.removeJavascriptInterface("myObject")

Java

webView.removeJavascriptInterface("myObject");

Only load web content over HTTPS

If you need to load untrusted content, ensure the WebView loads web content over an encrypted connection (see also our guidelines on Cleartext Communications). Prevent the initial page load from being performed on unencrypted connections by setting android:usesCleartextTraffic to false in the AndroidManifest file or disallow HTTP traffic in a network security config. See the usesCleartextTraffic documentation for more information.

Xml

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

To ensure that redirects and further app browsing doesn't occur on unencrypted traffic, check for the HTTP scheme in loadUrl or shouldInterceptRequest:

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

Validate untrusted content

If any external links are loaded in a WebView, validate both scheme and host (allowlist domains). Any domains not in the allowlist should be opened by the default browser instead.

Don't load untrusted content

If possible, only load strictly scoped URLs and content owned by the app developer into WebView.

Don't expose sensitive data

If your application accesses sensitive data with a WebView, consider using the clearCache method to delete any files stored locally, before using the JavaScript interface. You can also use server-side headers, such as no-store, to indicate that an application shouldn't cache particular content.

Don't expose sensitive functionalities

If your application requires sensitive permissions or collects sensitive data, ensure that it is called from code within the application and that prominent disclosure is provided to users. Avoid using JavaScript interfaces for any sensitive operations or user data.

Target API level 21 or higher

One secure way to use the addJavascriptInterface method is to target API level 21 or higher by ensuring the method is called only when running on API level 21 or higher. Before API 21, JavaScript could use reflection to access the public fields of an injected object.


Risk: MessageChannel risks

Lack of origin control in postWebMessage() and postMessage() could allow attackers to intercept messages or send messages to native handlers.

Mitigations

When setting up postWebMessage() or postMessage(), only allow messages from trusted domains by avoiding the use of * as the target origin, and instead explicitly specify the expected sending domain.


Resources