rspec/rules/S7409/kotlin/rule.adoc

143 lines
5.8 KiB
Plaintext
Raw Normal View History

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
[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(JavaScriptBridge(), "androidBridge") // Sensitive
}
inner class JavaScriptBridge {
@JavascriptInterface
fun accessUserData(userId): String {
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.
[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = false
}
}
----
If possible, remove the JavaScript interface after it is no longer needed, or before loading any untrusted content.
[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(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.
[source,kotlin]
----
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
}
}
)
}
}
----
== See
* Android Documentation - https://developer.android.com/privacy-and-security/risks/insecure-webview-native-bridges[Insecure WebView native bridges]
* Android Documentation - https://developer.android.com/reference/androidx/webkit/WebViewCompat[WebViewCompat API reference]
* OWASP - https://owasp.org/Top10/A05_2021-Security_Misconfiguration/[Top 10 2021 Category A5 - Security Misconfiguration]
* OWASP - https://owasp.org/www-project-mobile-top-10/2023-risks/m4-insufficient-input-output-validation.html[Mobile Top 10 2024 Category M4 - Insufficient Input/Output Validation]
* OWASP - https://owasp.org/www-project-mobile-top-10/2023-risks/m8-security-misconfiguration.html[Mobile Top 10 2024 Category M8 - Security Misconfiguration]
* CWE - https://cwe.mitre.org/data/definitions/79[CWE-79 - Improper Neutralization of Input During Web Page Generation]
=== Related rules
* S6362 - Enabling JavaScript support for WebViews is security-sensitive