Using JavaScript interfaces in WebViews to expose Java objects is unsafe. Doing so allows JavaScript to invoke Java methods, potentially giving
attackers access to data or sensitive app functionality. WebViews might include untrusted sources such as third-party iframes, making this
functionality particularly risky. As JavaScript interfaces are passed to every frame in the WebView, those iframes are also able to access the exposed
Java object.
Ask Yourself Whether
- The content in the WebView is fully trusted and secure.
- Potentially untrusted iframes could be loaded in the WebView.
- The JavaScript interface has to be exposed for the entire lifecycle of the WebView.
- The exposed Java object might be called by untrusted sources.
There is a risk if you answered yes to any of these questions.
Recommended Secure Coding Practices
Disable JavaScript
If it is possible to disable JavaScript in the WebView, this is the most secure option. By default, JavaScript is disabled in a WebView, so
webSettings.setJavaScriptEnabled(false)
does not need to be explicitly called. Of course, sometimes it is necessary to enable JavaScript,
in which case the following recommendations should be considered.
Remove JavaScript interface when loading untrusted content
JavaScript interfaces can be removed at a later point. It is recommended to remove the JavaScript interface when it is no longer needed. If it is
needed for a longer time, consider removing it before loading untrusted content. This can be done by calling
webView.removeJavascriptInterface("interfaceName")
.
A good place to do this is inside the shouldInterceptRequest
method of a WebViewClient
, where you can check the URL or
resource being loaded and remove the interface if the content is untrusted.
Alternative methods to implement native bridges
If a native bridge has to be added to the WebView, and it is impossible to remove it at a later point, consider using an alternative method that
offers more control over the communication flow. WebViewCompat.postWebMessage
/WebViewCompat.addWebMessageListener
and
WebMessagePort.postMessage
offer more ways to validate incoming and outgoing messages, such as by being able to restrict the origins that
can send messages to the JavaScript bridge.
Sensitive Code Example
public class ExampleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge"); // Sensitive
}
public static class JavaScriptBridge {
@JavascriptInterface
public String accessUserData(String userId) {
return getUserData(userId);
}
}
}
Compliant Solution
The most secure option is to disable JavaScript entirely. S6362 further explains why it should not be enabled unless absolutely
necessary.
public class ExampleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(false);
}
}
If possible, remove the JavaScript interface after it is no longer needed, or before loading any untrusted content.
public class ExampleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge");
// Sometime later, before unsafe content is loaded, remove the JavaScript interface
webView.removeJavascriptInterface("androidBridge");
}
}
If a JavaScript bridge must be used, consider using WebViewCompat.addWebMessageListener
instead. This allows you to restrict the
origins that can send messages to the JavaScript bridge.
public class ExampleActivity extends AppCompatActivity {
private static final Set<String> ALLOWED_ORIGINS = Collections.singleton("https://example.com");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
WebViewCompat.addWebMessageListener(
webView,
"androidBridge",
ALLOWED_ORIGINS, // Only allow messages from these origins
new WebMessageListener() {
@Override
public void onPostMessage(
WebView view,
WebMessageCompat message,
Uri sourceOrigin,
boolean isMainFrame,
JavaScriptReplyProxy replyProxy
) {
// Handle the message
}
}
);
}
}
See
Related rules
- S6362 - Enabling JavaScript support for WebViews is security-sensitive