Adventures with iOS UIWebviews
on 16 April, 2012
Recently MWR 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 platform’s 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, clients keep finding 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 client we are dealing with. The remediation and mitigation strategies also differ wildly from platform to platform.
When assessing the same mobile application on multiple platforms, the same issues can be found, but when we report back to the developers we are giving the iOS and Android developers different remediation and mitigation strategies. This is a point of frustration (for all involved) and what has ultimately led to us to produce this post.
In this post we’ll try and illustrate the differences between the OS X, Android and iOS WebKit implementations. we’ll specifically be concentrating on how to best implement a WebKit UIWebview in order to ‘reduce’ the likelihood of exploitation and to help ‘limit’ an attackers movements, should a compromise occur on the iOS platform (as this is the platform that offers the least amount of assistance!).
Before we delve into iOS though, it would be prudent to discuss Webview implementations and the ‘security’ features that are available on the Android and OS X platforms first.
Android Webview
The Android WebKit implementation allows the developer to modify a Webview through the android.webkit.WebSettings class.
A future post will detail how to specifically deploy a secure-as-it-can-be Android Webview.
OS X Webview
The OS X Webkit implementation is very different from the iOS implementation and is far more flexible. The OS X implementation allows the developer to modify a Webview through the WebPreferences class. The WebPreferences class can be used to disable support for Java and JavaScript via the instance methods setJavaScriptEnabled and setJavaEnabled. Additionally it is also possible to disable browser plugins via the instance method setPlugInsEnabled. The class does not implement the instance method setAllowFileAccess.
However the WebPolicyDelegate protocol in conjunction with the WebPolicyDecisionListener protocol allows developers to restrict the content that is loaded through URLs into a Webview. These policies can be implemented for Navigation requests, new window requests and by MIME type.
The WebPolicyDelegate protocol can be used to implement a policy delegate that uses 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 future post will detail how to specifically deploy a secure-as-it-can-be OS X Webview.
iOS UIWebview
So what options are available to iOS developers who need to ‘secure’ their Webview implementations? Well the iOS WebKit implementation makes use of the UIWebView class, which is a wrapper class around WebKit. Unfortunately Apple does not want to give developers control over it. Therefore, Apple does not make an API available to developers for enabling and disabling WebKit “features” (at least none that gives us enough control). There is no WebPreferences and/or WebSettings class and the WebPolicyDelegate protocol is also not available.
The Problem
One organisation we are aware of has a very specific (and somewhat common) problem whereby they must allow a third party to load remote content into a Webview (over a clear text channel) – basically advertising! The advertiser, like so many, does not offer the advertising feed via SSL/TLS (HTTPS). This content is also presented in a format that must be pre-processed before being written into a Webview. The solution to this particular problem must ensure that the 3rd party cannot abuse the privileges they have within the Webview to access local resources from the device file system. The 3rd party should also (ideally) only be able to load content and resources from a pre-approved domain. Finally the communications channel, ideally, must not be susceptible to a Man-in-The-Middle attack (as to prevent the injection of content and script into the Webview).
We began, like all good quests for knowledge do, with Google. We found a few forums asking similar questions – “How to disable JavaScript in a UIWebview” for example, the answer was most succinct – “you can’t”. Google tried to help by suggesting alternative search strings and presenting lots of links to OS X and Android solutions. We found nothing for iOS.
So we posed the question to colleagues, friends and family. we even went old skool and tried IRC! Finally we tried going Web 2.0 (like all the cool kids are doing) and tried asking the “twitterverse”. Unfortunately none of these efforts were fruitful. In the absence of any prescribed solutions, we need to explore alternative approaches and get a little creative!
HTTPS (SSL/TLS)
The Man-in-The-Middle issues could be mitigated significantly if the advertisement feed could be accessed via an SSL/TLS channel. iOS apps typically load remote content from web servers using the NSURLConnection class. This class takes an NSURLRequest object and performs a request with it.
There are some differences between the transports that are negotiated for different versions of the SDK, for example version 5.0 uses TLS 1.2 and offers 37 suites by default while 4.3 uses TLS 1.0 and offers 29 suites. However, in the case of the 5.0 SDK, none of the cipher suites offered are weak.
Prior to iOS5, the API is not granular enough to allow the developer to select which ciphers from the suite to negotiate with. Therefore developers need to explore alternatives, such as making use of 3rd party libraries (some of which, may or may not affect the app store approval process). However, the Security framework (Security.framework) in iOS5 now includes the Secure Transport interfaces. Secure Transport interfaces are Apple’s implementation of the SSL/TLS protocols. You can use these interfaces to configure and manage SSL sessions, manage ciphers, and manage certificates.
Resource and Content Inspection
The UIWebViewDelegate Protocol can be used, as illustrated below, to catch all requests and allow you to both manipulate them and pass them on, or to perform your own custom action. Example code is presented below. It simply catches the request and logs it before continuing with the load operation.
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
NSString *urlString = url.absoluteString;
NSLog(@"urlString: %@",urlString);
return YES;
}
The NSURL Class provides a way to manipulate URLs and the resources they reference. Several methods can be utilised to access parts of the URL so that it can be parsed and processed against defined whitelists/blacklists. A list of useful methods is presented below.
- absoluteString – An absolute string for the URL. Creating by resolving the receiver’s string against its base.
- standardizedURL – Returns a new NSURL object with any instances of “..” or “.” removed from its path.
- host – The host of the URL.
- scheme – The resource specifier of the URL (i.e. http, https, file, ftp, etc).
- pathExtension – Returns the path extension of a file URL.
- lastPathComponent – Returns the last path component of a file URL.
For example the following information can be extracted from the URL “http://labs.mwrinfosecurity.com/test.html” using the above methods.
- absoluteString – http://labs.mwrinfosecurity.com/test.html
- standardizedURL – http://labs.mwrinfosecurity.com/
- host – labs.mwrinfosecurity.com
- scheme – http
- pathExtension – html
- lastPathComponent – test.html
The methods can be used to implement decisions such as that illustrated below.
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
if (url.host != nil){
NSString *badHost = @"labs.mwrinfosecurity.com";
NSString *host = url.host;
if ([host isEqualToString:badHost]){return NO;}
else {return YES;}
}
This approach will allow you to detect an attempt to load a URL, trap it and parse it so that it can be inspected and compared against a blacklist/whitelist for processing. Should the URL be allowed, it can then be loaded.
Unfortunately what the approach cannot do, is prevent the authorised resource from loading content into the Webview from other resources dynamically using non interactive calls – such as <script src="http://www.attacker.com/malicous.js />, similarly use of XmlHttpRequest would not be intercepted for example. However IFRAME loads are trapped.
Note: we have not performed extensive tests on what is and is not trapped; the first fail is enough to prove this
approach is flawed.
Once intercepted and authorised, it is possible to perform a secondary check on the data that is returned. To this end, the NSURLConnection and the NSData classes can be used to inspect the content of the loaded resources. The NSData class method dataWithContentsOfURL returns a data object containing the data from the location specified by a given URL.
Therefore a potential solution would be to inspect the data returned for ‘suspicious’ and/or ‘malicious’ content.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
webView.delegate = self;
NSString *urlAddress = @"http://labs.mwrinfosecurity.com/test.html";
NSURL *url = [NSURL URLWithString:urlAddress];
NSData* HTMLData = [NSData dataWithContentsOfURL:url];
NSString* HTMLStr;
HTMLStr = [[NSString alloc] initWithData:HTMLData encoding:NSASCIIStringEncoding];
HTMLStr = [HTMLStr stringByReplacingOccurrencesOfString:@"script"
withString:@"REMOVED"];
[webView loadHTMLString:HTMLStr baseURL:nil];
[window makeKeyAndVisible];
}
The example above removes the text “script” and replaces with the text “REMOVED”. It is advisable to avoid doing this, of possible as again, this ‘solution’ is error prone and also means changing the content from remote to local and thus exposing access to the file system to any malicious script that manages to bypass the inspectors!
It would be more prudent to inspect the content and make an allow/deny decision. Both solutions would rely on a blacklist approach and would in all likelihood be trivially defeated by simply attacking the regular expressions implemented to bypass any inspections. Ultimately great care and attention to detail needs to be taken when implementing either.
BaseURL
We want to give a brief example, of what NOT to do when trying to prevent local resource access. Whilst reviewing an application recently we came across some code similar to the following.
NSMutableString *html = [NSMutableString stringWithString:@"<html><body>"];
[html appendString:HTMLContent];
[html appendString:@"</body></html>"];
[webView loadHTMLString:html baseURL:[NSURL
URLWithString:@"file:///some/bogus/path/to/prevent/filesystem/access"]];
The code above is used to load a Webview with the content retrieved from a remote 3rd party. However the developer has attempted to prohibit the 3rd party from using the Webview to load local resources by setting the baseURL to a non-existent file path. The parameter to the function loadHTMLString is not intended to be a security feature. Its intention is primarily to allow HTML to make relative links to resources within an applications bundle on the file system. For example the code below will load and execute the Javascript file script.js regardless of the baseURL setting.
NSMutableString *HTMLData = [NSMutableString stringWithString:@"<html>"];
[HTMLData appendString:@"<script src='file:////Users//nmonkee//script.js'></script>"];
[HTMLData appendString:@"</html>"];
[webView loadHTMLString:html baseURL:[NSURL
URLWithString:@"file:///some/bogus/path/to/prevent/filesystem/access"]];
Summary
If the advice is followed, with the caveats as outlined, then you’re implementing an “as-good-as-it-can-be” UIWebview. 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 (use iOS5).
- Ensure that the user cannot override and trust self signed or invalid SSL certificates.
- Inspect remote content via the use of the NSData class method dataWithContentsOfURL in an attempt to prohibit the loading of malicious script into a UIWebview.
- Do not load content remotely and then process the data returned before passing to a UIWebview (if at all avoidable) otherwise you grant local file system access to any malicious script that smuggles itself past your content inspectors.
- If you must load 3rd party content, make use of the UIWebViewDelegate Protocol to implement a white list in an attempt to ensure that the location is restricted (at least for interactive resource requests).