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.
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(JavaScriptBridge(), "androidBridge") // Noncompliant
}
inner class JavaScriptBridge {
@JavascriptInterface
fun accessUserData(userId: String): String {
return getUserData(userId)
}
}
}
class ExampleActivity : AppCompatActivity() {
private val ALLOWED_ORIGINS = setOf("https://example.com")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
WebViewCompat.addWebMessageListener(
webView,
"androidBridge",
ALLOWED_ORIGINS, // Only allow messages from these origins
object : WebViewCompat.WebMessageListener {
override fun onPostMessage(
view: WebView,
message: WebMessageCompat,
sourceOrigin: Uri,
isMainFrame: Boolean,
replyProxy: JavaScriptReplyProxy
) {
// Handle the message
}
}
)
}
}
| Note | If the interface cannot be replaced immediately, it can be removed before loading untrusted content. |
webView.addJavascriptInterface(JavaScriptBridge(), "androidBridge")
// ...
// Remove the interface before loading untrusted content
webView.removeJavascriptInterface("androidBridge")
webView.loadUrl("https://untrusted.example.com")