Adventures with Android WebViews
on 23 April, 2012
The majority of the mobile applications we have reviewed lately 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 often find that by reviewing the code base and/or performing an application assessment, vulnerabilities are discovered that can be leveraged specifically due to how a WebKit WebView has been implemented; however the level of compromise achievable and to what end, is very platform dependent. The level of compromise is obviously also dependent on the application itself and in most cases, specific to the case we are dealing with. The remediation and mitigation strategies also differ wildly from platform to platform. When we report back to developers we are often giving the iOS and Android developers different remediation and mitigation strategies. As part of this process we are often also asked to provide a “best practice” configuration guide for WebKit WebViews.
So, in this post I intend to provide details on how to implement an “as-good-as-it-can-be” WebKit WebView for Android applications.
Android WebView
The Android WebKit implementation allows the developer to modify a WebView through the android.webkit.WebSettings class.
Disable Support for JavaScript
If there is no reason to support JavaScript within the WebView, then it should be disabled. The Android WebSettings class can be used to disable support for JavaScript via the public method setJavaScriptEnabled.
webview = new WebView(this);
webview.getSettings().setJavaScriptEnabled(false);
This is disabled by default, but is good practice to explicitly disable.
Disable Support for Plugins
If there is no need to support plugins (such as Flash!), then disable support for plugins as these could be used as a vector to exploit your applications process. The Android WebSettings class can be used to disable support for JavaScript via the public deprecated method setPluginsEnabled.
webview = new WebView(this);
webview.getSettings().setPluginsEnabled(false);
Or the method setPluginState
webview = new WebView(this);
webview.getSettings().setPluginState(PluginState.OFF);
This is disabled by default, but is good practice to explicitly disable.
Disable File System Access
Should an attacker somehow find themselves in a position to inject script into a WebView, then they could exploit the opportunity to access local resources. This can be somewhat prevented by disabling local file system access. It is enabled by default. The Android WebSettings class can be used to disable local file system access via the public method setAllowFileAccess.
webview = new WebView(this);
webview.getSettings().setAllowFileAccess(false);
This restricts the WebView to loading local resources from file:///android_asset (assets) and file:///android_res (resources). The difference between a raw resource in res/raw directory and an asset is that assets behave like a file system; they can be listed, iterated over and discovered just like files. For raw resources you need the resource ID and are self-contained resources within the application package.
Resource Inspection
If it is necessary to support JavaScript within the WebView and/or a requirement to allow local file system access, then we need to be creative about how we implement the WebView as well as how we protect it from attackers. There is not a lot that can be done to prevent a Man-in-the-Middle attack, other than ensuring that the remote resources are loaded using cryptographically astute channels and that server certificate checking is enforced (whilst not allowing a user to override).
However, we can try to restrict the loading of 3rd party content/resources to those that are authorised. For example it is possible to inspect the requests for content and/or resources by overriding native methods.
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.
If a user interactively requests a resource from within a WebView it is possible through the use of the shouldOverrideUrlLoading method of the WebViewClient class to intercept the request. Example code is presented below.
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
private static final String LOG_TAG = "MWRLabs";
Log.d(LOG_TAG, "[x] getHost: " + Uri.parse(url).getHost());
Log.d(LOG_TAG, "[x] getScheme: " + Uri.parse(url).getScheme());
Log.d(LOG_TAG, "[x] getPath: " + Uri.parse(url).getPath());
if (Uri.parse(url).getHost().equals("labs.mwrinfosecurity.com")){return true;}
return false;
}
}
The method gives the host application a chance to take over the control when a new URL is about to be loaded in the current WebView. A return value of true means the host application handles the URL, while return false means the current WebView handles the URL. The code above prevents resources from being loaded from the host “labs.mwrinfosecurity.com”.
However, the method does not intercept resource loading from within, such as from an IFRAME or src attribute within an HTML or SCRIPT tag for example. Additionally XmlHttpRequests would also not be intercepted. In order to intercept these requests you can make use of the WebViewClient shouldInterceptRequest method. Example code is presented below.
@Override
public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
if (url.contains(".js")){return getWebResourceResponseFromString();}
else {return super.shouldInterceptRequest(view, url);}
}
private WebResourceResponse getWebResourceResponseFromString(){
return getUtf8EncodedWebResourceResponse(new StringBufferInputStream("alert('!NO!')"));
}
private WebResourceResponse getUtf8EncodedWebResourceResponse(InputStream data){
return new WebResourceResponse("text/css", "UTF-8", data);
}
The method notifies the host application of a resource request and allows the application to return the data. If the return value is null, the WebView will continue to load the resource as usual. Otherwise, the return response and data will be used. The code above intercepts requests for JavaScript resources (.js) and returns an alert instead of the requested resource. This is the preferred method.
The class URI can be used to parse the URL object and make decisions based on individual components. It is feasible to use pattern matching to inspect requests and prohibit those resources matching black lists. It is also possible to ensure that the WebView can only load content and make requests from resources from within a white list.
A list of useful methods is presented below.
- getHost – Gets the encoded host from the authority for the URI.
- getScheme – Gets the scheme of the URI (i.e. http, https, file, ftp, etc).
- getPath – Gets the decoded path.
For example the following information can be extracted from the URL “http://labs.mwrinfosecurity.com/test.html” using the above methods.
- getHost – labs.mwrinfosecurity.com.
- getScheme – http.
- getPath – /test.html.
If the pattern matching implementations are not carefully scrutinised, and a black list is implemented, then an attacker would likely look for a method to defeat the restrictions by simply attacking the regular expressions implemented to bypass any inspections. Ultimately great care and attention to detail needs to be taken when implementing this type of solution.
addJavascriptInterface
Developers often like to interact with their WebView via JavaScript from native code directly and to this end it is possible to implement a Java to JavaScript bridge. The addJavascriptInterface function from within the WebKit WebView class can be used to bind an object so that the methods can be accessed from JavaScript. This allows native code to be called from JavaScript executing within the WebView and for native Java methods to inject/call JavaScript functions within the WebView. Its use should be avoided, as the Same Origin Policy (SOP) does not apply to the bridge and therefore any exposed methods can be called from a child IFRAME even when running on another domain.
Summary
If the advice is followed, with the caveats as outlined, then you’re implementing an “as-good-as-it-can-be” WebKit WebView. The takeaway pointers are summarised below.
- Do not load content remotely over an unencrypted channel.
- Make sure that SSL/TLS connections are negotiated using strong ciphers.
- Ensure that the user cannot override and trust self signed or invalid SSL certificates.
- Prohibit local file system access.
- Disable JavaScript and Plugins.
- Intercept attempts to load remote resources and match against a white list in an attempt to prohibit the loading of unauthorised content into a WebKit WebView.
- Do not expose native methods to WebKit WebViews if it can be avoided.