Building Android Java/JavaScript Bridges

Recently we have been assessing a number of mobile Android and iOS applications. The majority of the applications we have reviewed make use of WebKit WebViews. WebKit is an open source web browser engine. A WebView is often used to load HTML content as an in process web browser to save passing the user off to the platforms web browser. They are also often used when a developer wants to quickly port a web application to multiple mobile platforms without having to create a specific UI for each. In addition to these ‘general’ use cases, we keep seeing ingenious ways to make use of them. The most common implementation that we come across is to facilitate advertisement loading from remote advertisers.

We’ve recently been performing an attack surface analysis against various platform WebKit WebView implementations. This post concentrates on our adventures with the Android platform.

As part of this research we came across a paper titled Attacks on WebView in the Android System, which made for interesting reading.

Our original intention was to create a series of posts that provide advice to platform developers on how to implement an “as-good-as-it-can-be” WebView. However, we found ourselves a little side tracked after reading this paper. In particular we were intrigued by section 4.2 “Attacks through Frame Confusion”.

Additionally, on our to do list, is to take a closer look at some of the frameworks that are available for cross platform development. Particularly solutions that allow developers to produce an application in one common language and ‘automagically’ push this application to all major mobile platforms, with very little or no effort at all.

This paper mentions one such solution that was on our radar PhoneGap. Therefore we took the opportunity to investigate a little.

PhoneGap is an HTML5 application platform that allows developers to author native applications using web technologies and provides access from a WebKit WebView to native code. The paper made mention that PhoneGap makes use of a Java/JavaScript bridge in order to allow HTML/JavaScript applications direct access to native code. There are many ways to attack a WebKit WebView; however the control that can be leveraged usually does not provide the opportunity to interact with Java code. So this makes for a very interesting attack vector.

addJavascriptInterface

It is possible to make use of the addJavascriptInterface function from within the WebKit WebView class to bind an object so that the methods can be accessed from JavaScript. Example code is presented below:

package com.mwr.bridge;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class WebViewGUI extends Activity {
  WebView mWebView;

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mWebView=new WebView(this);
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.addJavascriptInterface(new JavaScriptInterface(), "jsinterface");
    mWebView.loadUrl("file:///android_asset/www/index.html");
    setContentView(mWebView);
  }
  final class JavaScriptInterface {
    JavaScriptInterface () { }
    public String getSomeString() {
      return "string";
    }
  }
}

The method getSomestring() can be accessed from within the WebKit WebView as illustrated below.

<script>
var String = window.jsinterface.getSomeString();
</script>

The interface appears as an object within the DOM of the WebKit WebView; however it does not behave like a normal JavaScript object and cannot be enumerated over using reflection. For example the code below will not provide any results:

function init(){
  if(window.jsinterface){
    html.push('"window.jsinterface" IS defined');
    html.push('Properties of window.jsinterface are:');
    for(var property in window.jsinterface){html.push(property);}
    html.push('End of window.jsinterface properties');
  }
  else{html.push('"window.jsinterface" is NOT defined');}
    document.getElementById('myDiv1').innerHTML=html.join('<br/>');
  }

A JavaScript interface when implemented does not conform to the Same Origin Policy (SOP) either. Some example code is presented below:

public class JSInterface{
  public void getDomain(final String message){
    handler.post(new Runnable(){
      public void run(){
        StringBuffer StrbufHtml = new StringBuffer();
        StrbufHtml.append("javascript:alert('dom: ' + document.location + ' msg from: ");
        StrbufHtml.append(message);
        StrbufHtml.append("')");
        String js = StrbufHtml.toString();
        webview.loadUrl(js);
      }
    });
  }
}

The method getDomain() above can be called from a child IFRAME. When executed in the child frame, the code below will cause an alert to appear in the main parent frame. Even if the child frame is loaded from another domain.

<script>
window.jsinterface.getDomain(document.domain);
</script>

Without a mechanism to enumerate exposed methods ‘automagically’ any attacks would be blind, however if PhoneGap did make use of the same functionality, then these would be documented publicly and therefore a viable target for attack. However, we found, contrary to what is in the paper, PhoneGap does not use a JavascriptInterface. Checking out the latest build from SVN and searching the source for the method addJavascriptInterface confirmed this.

$ svn checkout https://svn.apache.org/repos/asf/incubator/callback/phonegap-android/trunk/
$ grep -r -n -i --include=*.java addJavascriptInterface *

Because PhoneGap does not use this interface, the PhoneGap APIs are not exposed and the SOP is intact – or so we thought! We were intrigued as to how PhoneGap does allow this interaction between JavaScript and Java to take place, if the WebKit WebView method addJavascriptInterface is not used. The author(s) of the paper may have reviewed an earlier version of the framework. We did a little digging to see if we could figure out why the method was not used.

We found that in the Android 2.3 SDK, the interface for creating the bridge between Java and JavaScript was broken. The specifics of the issue are detailed here.

So PhoneGap relying on this method needed a workaround and they found one. The most recent version of PhoneGap actually uses a ‘hack’ to provide a bridge and this is achieved through a technique known as “method overriding”.

In object oriented programming, method overriding is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has the same name, same parameters or signature, and same return type as the method in the parent class. The object that is used to invoke it will determine the version of a method that is executed. If an object of a parent class is used to invoke the method, then the version in the parent class will be executed, but if an object of the subclass is used to invoke the method, then the version in the child class will be executed.

In the com/phonegap/DroidGap.java source file there are three methods that are overridden – onJsAlert(), onJsConfirm() and onJsPrompt().

PhoneGap defines a callback/listener within the WebKit WebView that is notified whenever the JavaScript prompt() method is called (from JavaScript). In their callback method they check the parameters for an encoded task, execute it if found (or just return false if not) and then pass the result back to JavaScript via the JsPromptResult() as the onJsPrompt() function allows a string to be returned via the result object.

The following is what is sent when the API navigator.network.connection.type is called from JavaScript.

prompt('[]','gap:["NetworkStatus","getConnectionInfo","NetworkStatus1",true]');

In our tests from a child frame the PhoneGap APIs are not exposed and the SOP is respected; however as the override is set on the WebKit WebView, then anything in the WebKit WebView (including child frames) should have their call to prompt() overridden. Therefore it should be possible to invoke native code using the same method (i.e. by calling the JavaScript prompt() function).

We tested this in the local WebKit WebView and it worked. However a call to prompt() from an embedded child frame did not result in the execution of the native code, instead we got the actual prompt() call.

It turns out that this attack vector has been thought of before we got round to taking a look, the following is excerpt from the overridden method in the com/phonegap/DroidGap.java source file.

// Security check to make sure any requests are coming from the page initially
// loaded in webview and not another loaded in an iframe.
boolean reqOk = false;
if (url.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) {
  reqOk = true;
}

The code above performs a check to ensure that the call to the native code actually originated from the same domain as the parent of the WebView (which is likely to be file:///android_asset). It is difficult to inject into a local resource without initial compromise or a Cross Site Scripting vector. Therefore if a child frame attempts to call the method from a remote location, the call is not actioned by PhoneGap.

However the code above also checks the caller against a white list of allowed domains. Our next idea was to look into the implementation of the white list in order to look for potential ways to bypass. To this end, again someone has spotted an issue with the Perl-style implementation of regular expressions that is detailed here. Essentially an entry of http://www.my-website.com would allow access from http://www.my-website.com.evil-guy.com. The default policy allows access from domains such as http://127.0.0.1.evil-guy.com.

<?xml version="1.0" encoding="utf-8"?>
<phonegap>
  <access origin="http://127.0.0.1*"/>
  <log level="DEBUG"/>
</phonegap>

Summary

Our takeaway, until we find anything more fun, is when reviewing applications built using the PhoneGap framework, make sure you are looking in the PhoneGap.xml whitelist definitions file to see if there is scope for calling native Java from child frames bypassing the whitelist.

When reviewing Android applications, make sure you are looking for implementations of addJavascriptInterface as there could be some interesting attack vectors for calling native code.

PhoneGap is not the only solution out there for cross platform development and definitely not the only one to abuse the override onJsPrompt() hack either.