WebView JavaScript interfaces expose native application methods to JavaScript code running in an embedded web view. If the web view loads untrusted content, any script — including attacker-controlled code — can call those native methods directly.
Once a JavaScript interface is registered on a web view, the exposed native object is accessible to all JavaScript running in that web view, including scripts in untrusted third-party iframes. Web views may load content from sources that are not fully trusted, and JavaScript interfaces are propagated to every frame, not just the top-level page. An attacker who can inject or control JavaScript in any frame can therefore call the exposed native methods directly.
An attacker who controls JavaScript in the WebView — for example, through a malicious third-party iframe embedded in an otherwise legitimate page — can invoke exposed native methods without restriction. Depending on what the methods expose, this can lead to unauthorized access to sensitive user data, execution of privileged application functionality, or full compromise of the application’s security model.
The following code is vulnerable because it registers a native interface that is accessible to all JavaScript running in the WebView, including JavaScript from untrusted sources such as third-party iframes.
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"); // Noncompliant
}
public static class JavaScriptBridge {
@JavascriptInterface
public String accessUserData(String userId) {
return getUserData(userId);
}
}
}
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
}
}
);
}
}
| Note | If the interface cannot be replaced immediately, it can be removed before loading untrusted content. |
webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge");
// ...
// Remove the interface before loading untrusted content
webView.removeJavascriptInterface("androidBridge");
webView.loadUrl("https://untrusted.example.com");