Closed marazmarci closed 2 months ago
Thanks for the detailed issue, it's really awesome. And yeah, I knew about this issue and to be frank the "JavaScript Injection" example in the sample app and the accompanying feature really needs work, it suffers from me wanting to demonstrate and solve different things: I wanted to highlight the importance of sanitizing inputs in JavaScript and also disallowing running JavaScript from URI in loadUrl
. BTW disallowing javascript:
prefix would not be enough for the latter, data URLs can also be used to run JavaScript via loadUrl
, for example:
data:text/html,<script>alert('hi');</script>
They should be also either blocked entirely, or at least the usage of the <script>
tag.
Ultimately I think I'll rework the example and the library to just disallow running JavaScript via loadUrl
by blocking the javascript:
prefix and also disallow loading data URLs (by default) as there is the loadData
method for that, and I won't go into dealing with injection via parameters for this library (at least for now).
If you pass a malicious crafted URL containing percent-encoded characters to
loadUrl(String url, boolean escapeJavascript)
, injecting JavaScript code becomes possible, despite the protective measures implemented in the library.Since
WebView::loadUrl
expects a URI, it uses percent-encoding (URL encoding) to decode the URI, which makes it possible to add characters to the payload that would normally be escaped byStringEscapeUtils::escapeEcmaScript
. The"
character becomes%22
after URL encoding. If you use this instead of quotes, you can bypass the escaping.See my PoC (branch):
JavaScriptInjectionFragment.kt
:For this specific case, a possible fix could be to replace all occurrences of the
%
character with%25
, but I think that would break lots of other legitimate use cases, like when the URI doesn't start withjavascript:
, for example: https://www.google.com/search?q=I%27m+blueEscaping the whole
javascript:method("param")
string is also problematic, as it may not even contain user input (example:loadUrl("""javascript:alert("hello"))"""
). Only the user input should be escaped, but currently, that could only be the caller's responsibility, since the SecureWebView library can't know which parts of the JS string are untrusted user inputs, and which were written by the developer.Let's see some examples:
loadUrl("""javascript:alert("hello" + "$name")""")
name
can contain percent-encoded chars:loadUrl("""javascript:alert("hello" + "${StringEscapeUtils.escapeEcmaScript(name)}")""")
name
can contain percent-encoded chars:loadUrl(StringEscapeUtils.escapeEcmaScript("""javascript:alert("hello" + "$name")""")
loadUrl(StringEscapeUtils.escapeEcmaScript("""javascript:alert("hello")""")
, which will result in this exact String passed toloadUrl
:javascript:alert(\"hello\")
. It won't run, because\"
causes a JS syntax error.*: I think legitimate JavaScript calls should always happen through
WebView::evaluateJavascript
. I'm not aware of any reason why shouldloadUrl("javascript:...")
be used instead ofevaluateJavascript
, besides API 19 minSdk support, but fortunately the library's minSdk version is 21. Based on these, I suggest prohibiting passing URIs starting withjavascript:
toloadUrl
, and instead developing protective measures around theevaluateJavascript
method.I think the library could provide two things to prevent misusages of
evaluateJavascript
: 1.) Education in documentation or doc. comments :smile: 2.) Idea: a JavaScript method call builder, where the caller provides the method's name and the list of parameters to be passed, which all will be automatically escaped one by one. We could modify theevaluateJavascript
method to receive aJavaScriptCall
instance built by this builder. Also, we could add anevaluateJavascriptUnsafe
method which replicates the behavior of the originalevaluateJavascript
method.WDYT? :slightly_smiling_face:
:potato: