March 05, 2019

Securing Ionic 4 Cordova Apps

by Karan Gandhi

Securing Ionic 4 Cordova Apps

Ionic is a hybrid WebView based framework. Recently, the Ionic Team released their version 4. Traditionally, Ionic applications depend on the Cordova Framework to access Native APIs.

This article will cover some of the best security practices while developing Ionic Applications.

Analyzing Cordova Applications

Simply put, Cordova applications are websites running on smartphone WebViews inside separate application containers. The applications’ IPA/APK files are simple archives which can be uncompressed. If we check the assets folder, we can see the bundled files. Which makes the code easily readable. Hypothetically, by transferring the code to a debug APK container, we will have access to code + inspect mode.

In essence, secure Cordova apps must be developed with an assumption that:

  • The code is visible to the naked eye;
  • The code can be debugged & local storage is accessible.

As we’ll explore in the final section of this article, these weaknesses can be minimized with a JavaScript security solution such as Jscrambler.

Protect your Code with Jscrambler

With those assumptions in mind, let's look at some of the security strategies we can employ.

We will divide the article into the following sections

Securing App to Server Connection

A smartphone app communicates with a backend server via an API or web services. Traditionally, web services used an HTTP protocol. An HTTPS protocol adds a significant layer of security. Via HTTPS, the communication between the app and the server is encrypted.

Additionally, Apple introduced App Transfer Security (ATS) in iOS 9. ATS forces iOS Apps to connect over HTTPS protocol. Similarly, Android 9 Pie blocks cleartext traffic(HTTP). Devices with Android Pie will throw an error while communicating over HTTP unless the app explicitly allows it in the Network Security configuration.

SSL Pinning

Users/Apps are dependent on Certificate Authorities (CA) and Domain Name Servers (DNS) to validate domains for TLS. Unsafe certificates can be installed on a user device thereby opening the device for a Man-in-the-Middle attack.

With SSL Pinning, certificates can be bundled inside an application so the app doesn't have to rely on the device's trust store. In Cordova, we can implement certificate-based pinning via the advanced HTTP plugin.

Preventing External Code Execution

Like previously mentioned, Cordova apps are websites running in local WebView containers. As such, they are susceptible to XSS vulnerabilities. To prevent such execution, the application can be secured by using the Cordova Whitelist Plugin and a strict CSP.

Cordova Whitelist Plugin & Content Security Policy

The whitelist plugin allows us to define the security of an app via params in confix.xml. We can allow/block external navigation, control network access and restrict application intents.

The access tag allows us to restrict domains access in an app. By default, everything is allowed:

    <access origin="*" />

We can whitelist domains by replacing the * with individual domains. let's assume we have to restrict traffic to trusteddomain.com. The access tag doesn't allow whitelisting of WebSockets.

    <access origin="https://trusteddomain.com" />
    <access origin="https://api.trusteddomain.com" />

The allow-intent tag allows us to restrict calls to external apps via hyperlinks. By default, hyperlinks to a web browser, phone, SMS, email, and maps intent are enabled.

    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />

Another way of enforcing a whitelist is by adding a Content Security Policy (CSP) tag to the index.html page. CSP is a security standard used to prevent XSS attacks. You can read more about CSP here. Here is a sample tag:

<meta http-equiv="Content-Security-Policy" 
    content="default-src 'self' https://trusteddomain.com data: gap: https://ssl.gstatic.com; 
    style-src 'self' 'unsafe-inline'; 
    media-src *;
    >

The value gap: is required by iOS apps and https://ssl.gstatic.com is required by Android for Talkback. Other than that, most resources are whitelisted to trusteddomain.com. Furthermore, we can restrict the WebSockets and XHR using the connect-src attribute. Right now, it's implicitly blocked via the default-src.

Securing Local Storage

Local storage and the SQLite plugin are common methods to store persistent data in a Cordova app. Additionally, Ionic has a storage binding, which allows us to use IndexedDB, SQLite and local storage in an Ionic app using a single method. However, data in IndexedDB and local storage can be easily viewed in a debug app via browser debugging tools. Once retrieved, SQLite databases can be viewed via database viewers. We can secure data using two plugins in Cordova.

Secure Storage

The secure storage plugin allows us to store key-value data securely. In iOS, data is stored in Keychain via the SAMS keychain library. On Android, a random AES key encrypts the data. The encrypted AES key is then stored in SharedPreferences.

SQL Cipher

In some scenarios, we require SQL Tables in our app. In this case, we can use SQLCipher to secure the database.
SQL Cipher is a drop-in plugin for the SQLite cipher library. Data in SQLCipher is encrypted via 256 bit AES like Secure storage. The database is not readable without a key. However, the SQL key can be retrieved easily by unzipping the APK/IPA. As a precaution, it's advisable to generate a unique key for a user at the backend server. The generated key can be retrieved from the backend when the database needs to be opened. Alternatively, the key can be stored in secure storage.

Advanced Integrity Checks

These are individual checks which can be performed on an app. Root checks are used to determine whether a device is rooted. SafetyNet Attestation is used to check Device and APK Integrity. Jscrambler provides Code Locking, Self-Defending, and Obfuscation, which act as a JavaScript integrity check.

Root and Jailbreak checks

Rooted, Jailbroken or devices running custom ROMs should be considered insecure. Generally, custom ROMs allow users to use better and newer firmware on Android. However, a rooted device has read/write access to the /data and /system folders. This actually allows a user or an app to bypass native app security restrictions. It's advisable to run a root check while performing a critical functionality.

The iRoot plugin has Jailbreak and Root checks. For Android users, you can also check the RootBeer Plugin. It's a drop-in wrapper for the popular rootbeer library.

SafetyNet

SafetyNet is an Android-specific library to attest APK & Device Integrity. This functionality is only available on Play Certified devices or devices with PlayServices installed. The Attestation API is a rate limited function which provides a spot check for device and APK. VerifyApps provides functions for detecting harmful apps and enabling Verify Apps feature.

Here is the plugin for the SafetyNet API.

Code Obfuscation and Protection (Jscrambler)

As mentioned earlier, the code of Cordova applications can be easily retrieved. As so, it enables attackers to copy (re-distribute) the code, reverse-engineer sensitive logic, or even tamper with the code to abuse the app (such as unlocking features or violating license agreements).

This security weakness is best minimized with a JavaScript Application Shielding solution such as Jscrambler. Jscrambler provides four main security layers:

By protecting the source code of Ionic Cordova apps, reverse-engineering and tampering attempts become extremely difficult, as can be observed in the example below.

// Original Code Example
function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    m = checkTime(m);
    s = checkTime(s);
    document.getElementById('txt').innerHTML =
    h + ":" + m + ":" + s;
    var t = setTimeout(startTime, 500);
}

// Code Protected with Jscrambler (scroll right)
B100.P=function (){return typeof B100.H.C==='function'?B100.H.C.apply(B100.H,arguments):B100.H.C;};B100.H8=function(){var u8=2;while(u8!==1){switch(u8){case 2:return{C8:function W8(n8,S8){var F8=2;while(F8!==10){switch(F8){case 11:return f8;break;case 14:f8[U8][(c8+S8*U8)%n8]=f8[c8];F8=13;break;case 5:F8=i8<n8?4:9;break;case 3:i8+=1;F8=5;break;case 8:F8=U8<n8?7:11;break;case 4:f8[(i8+S8)%n8]=[];F8=3;break;case 9:var U8=0;F8=8;break;case 13:c8-=1;F8=6;break;case 7:var c8=n8-1;F8=6;break;case 1:var i8=0;F8=5;break;case 6:F8=c8>=0?14:12;break;case 12:U8+=1;F8=8;break;case 2:var f8=[];F8=1;break;}}}(14,6)};break;}}}();B100.x8=function (){return typeof B100.H8.C8==='function'?B100.H8.C8.apply(B100.H8,arguments):B100.H8.C8;};B100.G8=function (){return typeof B100.H8.b1==='function'?B100.H8.b1.apply(B100.H8,arguments):B100.H8.b1;};B100.l8=function (){return typeof B100.H8.b1==='function'?B100.H8.b1.apply(B100.H8,arguments):B100.H8.b1;};B100.B0=function (){return typeof B100.R0.C==='function'?B100.R0.C.apply(B100.R0,arguments):B100.R0.C;};B100.t1=function (){return typeof B100.a1.C==='function'?B100.a1.C.apply(B100.a1,arguments):B100.a1.C;};B100.s8=function (){return typeof B100.H8.C==='function'?B100.H8.C.apply(B100.H8,arguments):B100.H8.C;};B100.P8=function (){return typeof B100.H8.I1==='function'?B100.H8.I1.apply(B100.H8,arguments):B100.H8.I1;};B100.q=function (){return typeof B100.H.C==='function'?B100.H.C.apply(B100.H,arguments):B100.H.C;};B100.B1=function (){return typeof B100.a1.b1==='function'?B100.a1.b1.apply(B100.a1,arguments):B100.a1.b1;};B100.b8=function (){return typeof B100.H8.w0==='function'?B100.H8.w0.apply(B100.H8,arguments):B100.H8.w0;};B100.T8=function (){return typeof B100.H8.I1==='function'?B100.H8.I1.apply(B100.H8,arguments):B100.H8.I1;};B100.H=function(){var n=function(W,E){var a=E&0xffff;var J=E-a;return(J*W|0)+(a*W|0)|0;},z=function(O,N,b){var w=0xcc9e2d51,M=0x1b873593;var G=b;var l=N&~0x3;for(var R=0;R<l;R+=4){var i=O.charCodeAt(R)&0xff|(O.charCodeAt(R+1)&0xff)<<8|(O.charCodeAt(R+2)&0xff)<<16|(O.charCodeAt(R+3)&0xff)<<24;i=n(i,w);i=(i&0x1ffff)<<15|i>>>17;i=n(i,M);G^=i;G=(G&0x7ffff)<<13|G>>>19;G=G*5+0xe6546b64|0;}i=0;switch(N%4){case 3:i=(O.charCodeAt(l+2)&0xff)<<16;case 2:i|=(O.charCodeAt(l+1)&0xff)<<8;case 1:i|=O.charCodeAt(l)&0xff;i=n(i,w);i=(i&0x1ffff)<<15|i>>>17;i=n(i,M);G^=i;}G^=N;G^=G>>>16;G=n(G,0x85ebca6b);G^=G>>>13;G=n(G,0xc2b2ae35);G^=G>>>16;return G;};return{C:z};}();B100.s1=function (){return typeof B100.a1.w0==='function'?B100.a1.w0.apply(B100.a1,arguments):B100.a1.w0;};B100.W0=function (){return typeof B100.R0.C==='function'?B100.R0.C.apply(B100.R0,arguments):B100.R0.C;};B100.w1=function (){return typeof B100.a1.I1==='function'?B100.a1.I1.apply(B100.a1,arguments):B100.a1.I1;};B100.n1=function (){return typeof B100.a1.C==='function'?B100.a1.C.apply(B100.a1,arguments):B100.a1.C;};B100.C1=function (){return typeof B100.a1.b1==='function'?B100.a1.b1.apply(B100.a1,arguments):B100.a1.b1;};B100.c1=function (){return typeof B100.a1.I1==='function'?B100.a1.I1.apply(B100.a1,arguments):B100.a1.I1;};B100.R0=function(){var j0=2;while(j0!==1){switch(j0){case 2:return{w0:function(H0){var y0=2;while(y0!==14){switch(y0){case 2:var C0='',A0=decodeURI("A$+5%25%1B%7C%07%09%0E06%5C%02*%25%25%20v%3E=$%094M%3E%00%3C2%3EM$1%12.%1AL%14%1Bj%094M%3E%1654%3CF.6%0E06%5C%07,%3E%22'M9");y0=1;break;case 5:y0=K0<A0.length?4:7;break;case 1:var K0=0,i0=0;y0=5;break;case 8:K0++,i0++;y0=5;break;case 6:return function(q0){var V0=2;while(V0!==1){switch(V0){case 2:return C0[q0];break;}}};break;case 3:i0=0;y0=9;break;case 9:C0+=String.fromCharCode(A0.charCodeAt(K0)^H0.charCodeAt(i0));y0=8;break;case 4:y0=i0===H0.length?3:9;break;case 7:C0=C0.split('^');y0=6;break;}}}('(JEPWS')};break;}}}();B100.D8=function (){return typeof B100.H8.w0==='function'?B100.H8.w0.apply(B100.H8,arguments):B100.H8.w0;};B100.b0=function (){return typeof B100.R0.w0==='function'?B100.R0.w0.apply(B100.R0,arguments):B100.R0.w0;};B100.a1=function(A1){return{I1:function(){var P1,D1=arguments;switch(A1){case B100.x8()[7][6]:P1=D1[0]*D1[2]-D1[1];break;case B100.M0()[7][12]:P1=-(D1[2]*D1[3])-D1[4]+-D1[1]+D1[0];break;}return P1;},b1:function(d1){A1=d1;}};}();B100.R1=function (){return typeof B100.a1.w0==='function'?B100.a1.w0.apply(B100.a1,arguments):B100.a1.w0;};B100.M0=function (){return typeof B100.H8.C8==='function'?B100.H8.C8.apply(B100.H8,arguments):B100.H8.C8;};function B100(){}B100.v0=function (){return typeof B100.R0.w0==='function'?B100.R0.w0.apply(B100.R0,arguments):B100.R0.w0;};B100.K8=function (){return typeof B100.H8.C==='function'?B100.H8.C.apply(B100.H8,arguments):B100.H8.C;};function startTime(){var I0=B100;var B,K,g,T,d,Y,r,I;B=new Date();K=B[I0.b0(1)]();g=B[I0.b0(7)]();T=583587531;d=-1024664412;Y=2;for(var o=1;I0.q(o.toString(),o.toString().length,44684)!==T;o++){r=B[I0.v0(4)]();g=checkTime(g);Y+=2;}if(I0.q(Y.toString(),Y.toString().length,49201)!==d){r=B[I0.v0(4)]();g=checkTime(g);}r=B[I0.v0(6)]();g=checkTime(g);r=checkTime(r);I0.C1(I0.x8()[8][12]);var o0=I0.w1(4,67,18);I0.B1(I0.x8()[4][8]);var c0=I0.w1(93,10,7,10,8);document[I0.v0(3)](I0.b0(2))[I0.v0(0)]=K+I0.b0(o0)+g+I0.b0(c0)+r;I=setTimeout(startTime,500);}

To get started with protecting Ionic/Cordova source code with Jscrambler, check the official guide.

Final Thoughts

This article provides an overview of techniques for hardening your Cordova-based Ionic applications. Depending on your use case, you can create a viable architecture for your security needs.

If you want to ensure that your own Cordova/Ionic apps are not exposed on the client-side, request a Jscrambler demo or try it for free.