iOS/Android: (Un)Secure Apache Cordova Apps

Background information.

The advent of cross-platform development frameworks such as Apache Cordova breathed new life into the old slogan of „Write once, run anywhere“ then invented by Sun Microsystems.
In most cases, „write once“ translates directly into „write in JavaScript“, as it is the language of the web, or rather the WebView component on the mobile platform.
The JavaScript code is then typically interpreted at runtime by JavaScriptCore, the JavaScript engine that powers Apple’s Safari.

Unfortunately, such cross-platform comfort comes at a price—reduced tamper resistance on the mobile device.

  • While reversing native code is more often than not a tough, time-consuming endeavor, this does not hold true for JavaScript code, even if obfuscated (https://cordova.apache.org/docs/en/latest/guide/appdev/security/#do-not-assume-that-your-source-code-is-secure), and JavaScript code that is easy to reverse and understand is also easily modified.
  • The content of the app’s binary is furthermore protected by a device-specific encryption. Making the retrieval of the unencrypted binary sometimes a time-consuming task, if the app is properly protected against debuggers. This does also not apply to the JavaScript code, this code is usually directly read-able or only obfuscated.
  • Native code signature checks do not cover JavaScript resources. For example, Apples code signature checks “ensure that an app hasn’t been modified since it was installed or last updated, but are limited to “executable memory pages”. Thus, if JavaScript code is modified after an app has been installed, such modifications will not be detected.

Of course, this is a known phenomenon. But awareness may still be lacking, especially among managers which assume that their developers can build high risk applications with a low-budget.

Technical Details.

When developing an mobile app with Apache Cordova the content of the distributed app bundle will look like the following:

|_ XXX.app
    |_ Frameworks
    |_ www
        |_ assets
        |_ build
            |_ 0.js
            |_ 1.js
            |_ ...
            |_ main.js
        |_ plugins
        |_ cordova.js
        |_ corodva_plugins.js
        |_ manifest.json
        |_ service-worker.js
        |_ index.html
        |_ ...
    |_ config.xml
    |_ ...

As stated earlier the JavaScript (*.js) files inside of the bundle can be modified. The countermeasure most developers take is obfuscation, using tools like Jscrambler. This makes the JavaScript code hard to reverse-engineer and thus makes the process of discovering vulnerabilities or weak security-feature implementations a time consuming task. As a penetration tester, time is always a limited resource, as it is for a real adversary… 
Therefore, wouldn’t it be handy to find a way to circumvent security measures by not touching the JavaScript code nor the app’s main binary? 

Fortunately I discovered an alternative method, that proved to work on several apps (affected apps will not be disclosed here). When going back to the previous directory listing, showing the content of the mobile apps bundle, next to all the .js files there is also a file called config.xml that can be seen. This file must be present in every Apache Cordova app. It is created during an early stage of the development process and extended if needed. It provides information about the app and specifies e.g.the plugins that are used. 

Let’s take a look at the following exemplary extract from such a config.xml file:

 […]
 <feature name=„Diagnostic“>
 <param name=„ios-package“ value=„Diagnostic“ />
 <param name=„onload“ value=„true“ />
 </feature>
 <feature name=„SSLCertificateChecker“>
 <param name=„ios-package“ value=„SSLCertificateChecker“ />
 </feature>
 <feature name=„AppCenterShared“>
 <param name=„ios-package“ value=„AppCenterSharedPlugin“ />
 <param name=„onload“ value=„true“ />
 </feature>
 […]

As you can see in this specific app an ios-package called SSLCertificateChecker is included. This plugin is used to verify the identity of the corresponding backend by checking which certificate it presents. It will detect a Machine-in-the-Middle (MitM) attack, even if the victim of the attack has installed and trusted the attacker’s CA certificate. This method is called SSL-Pinning and is a common counter measurement against MitM attacks.

The following example shows a method to stop the app from successfully loading this protection-feature:

 [...]
 <feature name="Diagnostic">
 <param name="ios-package" value="Diagnostic" />
 <param name="onload" value="true" />
 </feature>
<!-- Do not use SSLCertificateChecker
 <feature name="SSLCertificateChecker">
 <param name="ios-package" value="SSLCertificateChecker" />
 </feature>
-->
 <feature name="AppCenterShared">
 <param name="ios-package" value="AppCenterSharedPlugin" />
 <param name="onload" value="true" />
 </feature>
 [...]

As can be seen in the previous example, the SSL-Pinning feature element has been commented-out (it can also be deleted). This patch can be either applied to a config.xml file present in an app’s bundle path (/var/containers/Bundle/Application/xxx-xxx-…/XXX.app/config.xml) on the iOS device itself or to a dumped .ipa file. If the modification takes place in the .ipa file, the contents must be re-signed with a valid Apple-Developer account before installation. The modification of the config.xml file will be loaded on the next app start.

If the patch of the config.xml file worked, the SSL-Pinning check will no longer be present and the network-traffic can e.g. be analyzed with a HTTP-proxy like BurpSuite or OWASP Zed Attack Proxy (ZAP).

This patch also proved to work on the counterpart for Android affected by this same issue. The config.xml file can be found in the .apk (./res/xml/config.xml).

Impact.

I downloaded many mobile apps for financial and medical stuff from the iOS App Store. 25% of the Apps that were build upon Apache Cordova suffered from the described vulnerability.

Conclusion.

In summary, if your adversary model includes a local attacker with elevated privileges, you should verify that all plugins were loaded successfully, if not the app should shut-down immediately.
As always, the real objective here is to move the goalpost out of reach of opportunists and increase the cost incurred by advanced malicious actors. An advanced adversary will eventually be able to surpass even Apple’s binary code signature checks.

Kudos.

Thanks @mtschirs for review and recommendations for this text.