“Gartner Listed - mobile application security guide”

April 28, 2019

OWASP Mobile Top 10 2016: M2 – Insecure Data Storage

This is the second article in our OWASP Mobile Top 10 series, which aims to flesh-out the OWASP recommendations with some concrete examples that you can apply to your iOS and Android applications today.

If you haven’t already please check out our OWASP Mobile Top 10 2016:M1 – Improper Platform Usage article, which has some great advice around platform security features such as the Keychain, TouchID, Android Intents and platform permissions.

Careless use of the mobile platform’s storage can result in a host of potential issues for the end user, including identity theft and fraud. For enterprise applications this area of mobile security can have huge repercussions including damage to the company’s reputation or even the violation of external policies such as PCI compliance.

Access to Backup Data

To the end user a reliable local backup of their data is a great way to secure their investment should something happen to the device. Unfortunately for the developer this represents an easy way for your data to be distributed and copied outside of the app’s sandbox, and in turn the filesystem protections enforced by the mobile OS.

iOS – iTunes Backup

iOS users can use the iTunes desktop software to backup the device data to their laptop if they wish. However, Apple have made changes to their backup encryption for iOS 10, making the backup data encryption much easier to crack.

The easiest way to avoid issues with iOS backups is to ensure that any sensitive data that can be easily recovered from back-end systems is not backed up at all. To achieve this all you need to do is to store the sensitive data in the Library/caches folder:

let fileURL = try! FileManager.default
    .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    .appendingPathComponent("data.dat")

Android – ADB Backups

The Android Debug Bridge (ADB) tool can be used without root access by an end-user or an attacker with access to the device. This capability is enabled by default across most Android devices, and represents a major concern – especially for enterprise applications.

Disabling ADB backups for your application is relatively straightforward. All you need to do is add android:allowBackup=”false” to the Application section of your app manifest. After this change your application data should not be included in any backups initiated by ADB or any other backup tools that make use of its functionality.

Local Files

If an attacker has access to the physical device it can be very easy for them to gain access to app data and media files depending upon the device security settings. Enterprise IT departments should push out policies to enforce device features like automatic device locking and passcodes where possible.
Most applications will need to store session information to ensure that the application remains in a consistent state between uses. Some apps will also read configuration information from files or even allow this configuration to be overwritten by a back-end system. In all of these cases the configurations files could be modified by an attacker. Where possible you should move this configuration data to the application binary, especially if it defines API endpoints or access keys.

iOS – Filesystem Access

Desktop apps like iPad File Explorer make access to the device filesystem very straightforward, without jailbreaking or using specialist security tools. For the storage of small amounts of data such as passwords, keys and certificates the iOS Keychain should be your preferred option rather than standard files or NSUserDefaults. You should also ensure that you’re using the most protective attributes possible for your Keychain items, such as kSecAttrAccessibleWhenUnlocked.

When storing data in standard files on disk you should ensure that the data is always stored with the NSFileProtectionComplete flag set. This flag ensures that the data is encrypted at rest whenever the device is locked or booting, reducing the effectiveness of external file browser tools.

When storing especially sensitive data on either the filesystem or Keychain you should also make use of the iOS encryption facilities to ensure that your data is secure. The easiest way to implement your own encryption on iOS is to use the SecKey family of functions, such as SecKeyEncrypt and SecKeyDecrypt. This will ensure that your data is secure, even if the Data Protection API is compromised. For more information check out the Security Frameworkreference.

Android – Filesystem Access

Android’s SharedPreferences stores data unencrypted on the filesystem. These files are protected by standard Linux-style file permissions, restricting access to only the application binary. On a rooted device these files are easily accessible however, so you should never store sensitive information here without encrypting it first.
When searching for file encryption examples for Android be aware that many of these include use of the Crypto provider and the “SHA1PRNG” algorithm. Unfortunately the SHA1PRNG is no longer considered cryptographically strong and as a result Google has deprecated the Crypto provider and SHA1PRNG algorithm in Android Nougat. The correct way to implement encryption is outlined in this article on the Android Developers Blog.

HTTP Caching

Many developers are unaware of the security implications of HTTP caching. By default most mobile HTTP clients will cache as much data as they can to try to increase app performance. Data can also be cached by proxies between the client and the server, potentially storing your data in many distributed locations.

The operation of the cache is controlled on the server-side by the HTTP cache-control header.

The iOS HTTP clients will generally refrain from caching data that’s sent over HTTPS, but it’s important to verify this manually or via the documentation. It’s also worth considering that data such as over-the-air application configuration may be sent over HTTP and not considered sensitive. The manipulation of this data may however expose the inner workings of your application or even change API endpoints.

iOS – URLCache

Most of the popular HTTP clients on iOS such as AFNetworking or AlamoFire use the standard URLCache class for caching data. URLCache will store your HTTP cache to disk unencrypted, in a database file that is unique to each application. An attacker with access to the filesystem can extract the contents of the cache database quite easily using a tool such as File Juicer.

Managing the cache on iOS can soon get quite complex. The cache is actually split across memory and storage, and there are many settings that can affect the caching policy. Confusingly, methods such as removeAllCachedResponses() only remove data from the in-memory cache, so it’s best to avoid these methods if you’re dealing with sensitive data.

The best way to manage the data in the cache storage is actually to avoid caching it in the first place. You can either set the disk and memory cache size to zero, or if you’re comfortable with in-memory caching you can leave this as a higher value:

URLCache.shared.memoryCapacity = 0;
URLCache.shared.diskCapacity = 0;

Disabling the cache completely can cause issues for app performance however, so you’ll only want to do this for your sensitive data if possible. You can do this by implementing the connection(_:willCacheResponse:) method of NSURLConnectionDataDelegate and returning a nil response.

Android – Volley and OkHttp

Most Android developers will move away from the platform’s HTTP functionality in favour of more full-featured clients like OkHttpVolley or other frameworks that build upon them. Due to the automatic dependency resolution in Gradle and Maven it’s important to check the dependency tree to see which libraries you’re actually using. You can do this by running ./gradlew <app>:dependencies in the main folder of your application, where <app> is your module’s name.

Cache control with Volley is driven entirely by the cache-control headers sent by the server. This can be a convenient way to manage your cache functionality but might cause issues if you don’t have access to the server code that manipulates the headers, for example when using a 3rd party web API. If this is the case it is possible to disable the caching on a per-request basis like so:

request.setShouldCache(false);
myQueue.add(request);

For OkHttp things are a little more complicated as unfortunately it’s not possible to disable caching on a per-request basis. Instead you’ll need to create a new OkHttpClient without a cache. You can then use this new OkHttpClient for your sensitive requests:

private OkHttpClient createSecureHttpClient() {
    return new OkHttpClient.Builder()
            .readTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60 / 2, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .cache(null)
            .build();
}

Local Structured Data

Many apps will outgrow the capabilities of static files and require more advanced data storage. There are many options available, including databases and object graphs along with the possibility for synchronisation of the data to your back-end. As these more complex systems abstract the underlying storage and transport it is important to understand what’s going on under the hood and act accordingly.

SQLite

SQLite is a simple and popular file-based database, included with both iOS and Android. The version included with the operating system can and does change over time, with details for iOS and Android here. If you’re concerned about a security issue in an older version you should limit the minimum OS version for your app’s installation package accordingly.

SQLite’s straightforward approach to storing its data in one binary file is one of the main reasons that it’s so popular, especially in resource-constrained environments. This is also its main concern from a security perspective. The file is not encrypted and is easily browsed if retrieved. Because of this the concerns outlined in the Local Files section above are just as relevant for SQLite data.

One of the most popular approaches to security SQLite is to use the SQLCipher extension from Zetetic. Though it’s a commercial product an open source edition is available, along with a pod for iOS. Android Gradle integration is a little more involved however.

iOS – Core Data

The features offered by Core Data can be very compelling when compared to standard dictionaries or arrays. By default the data is stored unencrypted without any filesystem protections however.

A quick security fix for non-sensitive data is to set the NSFileProtectionComplete flag on the Core Data SQLite store. This ensures that the data is encrypted at rest when the device is locked or booting:

let description = NSPersistentStoreDescription()
description.setOption(FileProtectionType.complete, 
forKey: NSPersistentStoreFileProtectionKey)
container.persistentStoreDescriptions = [description]

For especially sensitive data you may wish to handle the encryption manually. For SQLite-backed data stores the Encrypted Core Data project might come in handy. This wraps up SQLCipher for encryption along with ensuring that the API is the same as using Core Data directly.

Codified Security is here to help make your mobile app secure whether it’s for iOS, Android, or to make sure you’re clearing the OWASP Mobile Top 10.