Since Android WebViews are browser controls in an app, they invite traditional web attacks. Learn how to protect against Android WebView attacks.
It’s been several months since I presented on Android WebViews at OWASP AppSec EU 2015 in Amsterdam, and I finally have the chance to put the content into a series of posts. In this first part of the series, I’ll briefly introduce WebViews and discuss the first of several topics, namely the JavaScript bridge. Other topics including HTTPS, the SOP, URL scheme handling, and local storage will be covered in posts to follow.
A WebView is a control to load and display web content as well as provide basic browser features such as navigation history and a JavaScript execution environment. Control of the embedded WebView is given to an app via callbacks (Java interfaces) through which the app can react to, modify, or reject events (a WebView may also be customized via the WebSettings class). The two most important callbacks are WebChromeClient for browser events and WebViewClient for web events. For example, an app can intercept a page load (URL loading event) by overriding shouldOverrideUrlLoading(). In theory, this design allows the app to intercept any event that loads content. In practice, however, this is not always the case.
The Pre-KitKat (Android 4.4) WebView was largely based on WebKit and did not receive software updates (unless a device received an OTA update from the carrier or OEM). KitKat and later WebViews are based on the chromium open source browser. Since Android 5.0 (Lollipop), the WebView is packaged as a separate APK and is updated separately.
It is “the” (embedded) component that powers the majority of HTML-enabled apps and many internet apps. WebViews continue to be a current topic but for the wrong reasons. Fortunately (or unfortunately), none of these reasons are new.
Firstly, since a WebView is a browser control in an app, it invites traditional attacks associated with the web: connection hijacking, XSS, and so on. But WebViews sport other features (since the use of a WebView is implicit, we will just refer to them as apps). The developer can, by design, punch holes in the sandbox. Web content can interact with the app and vice versa. This design means that if a vulnerability exists then it can be exploited in either direction. Moreover, a common (even pervasive) model of apps is to bundle both local resources and web content in the same container (i.e. the app itself). When put together, the resulting threat model becomes more than the sum of its parts. A same origin policy (SOP) bypass can lead to device file-system access. Think stealing user data or cookies. Incorrectly processing URLs can make the app an intermediary. Think remote attacker targeting other applications (or even the app itself) by using the intermediary app as a proxy. We’ll explore these in this and forthcoming posts.
Figure 1: A WebView combines both the threat models facing traditional and web-based apps
The JavaScript to Java bridge (a.k.a. JavascriptInterface) is a WebView feature to programmatically install a Java object into the web page to be accessible from JavaScript. This is shown below in which the Java code injects an object into the WebView (lines 1 and 2) and script on the page calls the object’s public method (line 4).
Figure 2: Using JavascriptInterface
Note that the addJavascriptInterface() has no notion of origin and the installed JavascriptInterface is therefore not governed by the SOP—making it dangerous by design.
It is now well well-known that JavaScript bridges can be abused (Google). See Figure 3 for an example. This was first disclosed publicly by Neil Bergman. JavaScript can use reflection to access a JavaScript bridge’s public fields (lines 1 and 2) to execute Java code (line 3) with the permission of the host application.
Figure 3: JavascriptInterface reflection attack
The @JavascriptInterface annotation was introduced in Android 4.2, JELLY_BEAN_MR1 (Security Enhancements in Jelly Bean) to limit exposure to the reflection attack. Developers label methods with @JavascriptInterface and the run-time constrains JavaScript access to the annotated methods only. If you test this, then an unauthorized access to an unlabeled method results in an error:
I/chromium(13478): [INFO:CONSOLE(1)] "Uncaught TypeError: Object [object Object] has no method 'secret'"
However, @JavascriptInterface comes with plenty of caveats. In order to be effective against the reflection attack, the app must use the @JavascriptInterface annotation and target API level 17 or higher (targetSdkVersion = API level 17), and the app must be run on Android 4.2 or later (the interface can still be controlled by unauthorized JavaScript). If, for example, the app uses the @JavascriptInterface annotation, targets API level 17, and is run on 4.1 or earlier (e.g. because minSdkVersion allows it) then the app is still vulnerable as the annotation is ignored (we assume that the WebView has JavaScript enabled).
In other words, for @JavascriptInterface to be useful as a fix:
For users on earlier devices the threat cannot be directly addressed and mitigation is by means of compensating controls (i.e. measures that prevent the execution of untrusted JavaScript).
The short answer is NO.
Let’s first restate what this means: You MAY be vulnerable to the JavascriptInterface reflection attack (for Android < 4.2), even if you don’t install a JavascriptInterface!
This is because, in some Android versions (4.0.0 – 4.1.2), core WebView code creates at least one built-in JavaScript Interface in every implemented WebView. So every single application that loads WebView content over HTTP (or may somehow otherwise load untrusted JavaScript) is vulnerable to this attack in a device running the affected Android versions.
Developers can adopt a defense-in-depth approach by proactively removing system-installed bridges.
This presents a problem: if you haven’t installed these bridges, how can you name them in order to remove them? There are three possibilities, none of which is pretty:
Targeting JELLY_BEAN_MR1 or later still leaves apps vulnerable to the reflection attack on devices pre-4.2. At the time of writing, this represents approximately 20% of all devices. It’s possible to exclude these by setting minSdkVersion to 17. If you want to reach these devices AND you don’t need JavaScript to Java bridge functionality, you can use the method in the previous section to remove hidden JavascriptInterfaces. If you use a JavascriptInterface, then untrusted script injected into the page can still cause damage. Address this risk as you would normally do—for example, prevent XSS—and make sure the SSL channel is properly secured.