Replacing addJavascriptInterface() with HTML Message Channels

One item in Google���s app security checklist
caught my eye late last week:




WebViews do not use addJavaScriptInterface() with untrusted content.



On Android M and above, HTML message channels can be used instead.




I had glossed over HTML message channels back in
my Android 6.0 ���random musings��� post,
and I was somewhat at a loss as to how to use them to replace addJavascriptInterface().



Fortunately, Diego Torres Milano
came to my rescue,
pointing out a CTS test case for these APIs. From there, I
was able to figure out how this stuff works,
more or less.



The classic ways we have used to bridge Java and JavaScript, in the context
of a WebView, are:





loadUrl("javascript:...") and evaluateJavascript(), to have Java invoke
JavaScript code




addJavascriptInterface() to expose a Java object as a notional JavaScript
global, so that JavaScript can call methods exposed on that object





The latter has been the source of many security bugs over the years, which presumably is
why Google is counseling against it.



HTML message channels are a viable alternative, for cases where you control
the Web content being shown in the WebView.



postWebMessage() is a method on WebView that allows you to send over an
arbitrary string to the JavaScript code in the currently loaded Web page.
To receive it, though, the JavaScript must have registered a global
onmessage function. So, postWebMessage(new WebMessage("Hi"), ...) will
cause the following onmessage function to be called, with e.data being
"Hi":



onmessage = function (e) {
parse(e.data);
}


This can serve as a replacement for loadUrl() and evaluateJavascript(), for
Java telling the JavaScript in the current Web page to do something��� if and
only if the JavaScript is set up to listen to those messages.



Replacing addJavascriptInterface() is substantially more involved, including:





Calling createWebMessageChannel() on the WebView, to get a WebMessagePort
pair representing a communications channel




Passing one of those ports to JavaScript via postWebMessage()




Each side, as needed, registering handlers to listen for messages on those ports




Each side, as needed, sending messages to the other side via those ports





My Stack Overflow answer has a
bit more detail. This sample app
shows it in use, and I���ll be covering this in greater detail in the next update
to The Busy Coder���s Guide to Android Development.



There are some limitations, though:





You have to modify the JavaScript code to work with these message ports, which
should not be a huge issue, given that you had to modify the JavaScript to
talk to your injected notional JavaScript global from addJavascriptInterface()




A bug or undocumented limitation
requires you to use loadDataWithBaseURL()
and an http or https URL to make this work. You cannot, for example, use
this with loadUrl("file:///android_asset/..."), which confused the heck out
of me when I was trying to go that route in the sample app.




This is only available on Android 6.0+, which means until your minSdkVersion
is 23 or higher, you have to use the other APIs as well, at least for the older
devices that you are supporting.





Is this more secure than addJavascriptInterface()? Probably, insofar as the
scope of the API that you expose from Java to the JavaScript code is more intentional.
It is more difficult to accidentally expose something when it is more difficult
for you to expose anything.
However, neither HTML message channels nor addJavascriptInterface() are great solutions
for when you are working with arbitrary HTML/JS that you did not write.

1 like ·   •  0 comments  •  flag
Share on Twitter
Published on January 23, 2017 10:11
No comments have been added yet.