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.
There is a risk if you answered yes to any of these questions.
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.
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.
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.
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);
}
}
}
The most secure option is to disable JavaScript entirely. {rule:java: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
}
}
);
}
}