Skip to Main Content
December 01, 2022

Looting iOS App's Cache.db

Written by Drew Kirkpatrick

Insecure By Default

Mobile application assessments diverge somewhat from normal web application assessments as there is an installed client application on a local device to go along with the backend server. Mobile applications can often work offline, and thus have a local store of data. This is commonly in the form of SQLite databases stored in the application's filesystem "sandbox".

As part of a mobile application assessment, TrustedSec reviews how data is stored and cached in these local files. Sensitive information should not be stored in cleartext in the local files of an application. Both Android and iOS provide secure methods of storing sensitive data, by using the Android Keystore and the iOS Keychain.

These secure storage tools are perfect for storing session tokens, cookies, passwords, and encryption keys. While you may not wish to store a large amount of data in the Keychain or Keystore, you can easily encrypt a SQLite database using tools such as SQLCipher and keep the encryption key in the Keychain or Keystore.

You would think that such tools would lead to an abundance of encrypted databases in mobile application development. Unfortunately, we very rarely see SQLite databases encrypted in the local file store.

Fig.1 - Me finding sensitive data in local storage. Again.

But application developers are not the only ones to blame here. iOS provides a URL loading system to make application development easier. The NSURLSession classes help developers to get communication between their application and the back-end server up and running.

By default, this library will cache network traffic, in the application's local files, in an unencrypted file called Cache.db. This is a SQLite database we see on nearly all iOS application assessments.

Fig.2 - How bad could it be?

So what do we find in Cache.db? More than 90% of the time, we find session cookies and tokens. That's not great as this information can lead to account compromise. We also find data contained in server responses cached in this database, potentially disclosing that information as well.

And fairly often, we'll find the full cleartext credentials for the user.

Fig.3 - That's some tasty loot

Before we cover how you would recover this information from the Cache.db file, I want to cover how an attacker might get their hands on this file. iOS and updated Android devices both provide strong security controls. Files such as Cache.db are stored in the application's sandbox, making access to these files challenging for attackers.

The most obvious attack path to acquire the local data is to gain access to the unlocked device, which is not an easy task.

A more likely avenue to access these files is from a device backup. Specifically for an iOS device, this could be through compromising an iCloud account, or gaining access to the desktop computer that contains a local backup of the device. Application developers have to specifically disable backups of their applications or data. I frequently retrieve Cache.db files from an iOS backup.

Fig.4 - Making iPhone backup

You can find these backup files on the local computer in the following locations:

macOS: ~/Library/Application Support/MobileSync/Backup
Windows: %APPDATA%\Apple Computer\MobileSync\Backup

For the iOS backup, all folder and filenames will be replaced with hashes. This makes using the backup directly rather difficult, however the iExplorer tool can make this significantly easier. iExplorer can extract the local data files for the application, and can even decrypt the backup if you have access to the password used to create the backup.

Fig.5 - Local application data in iExplorer

Once you extract the local data folders from iExplorer, you can start searching for sensitive data. The Cache.db folder is by default located in /Library/Caches/APPBUNDLEID/Cache.db.

Let's look at how you extract potentially sensitive data from this database. I'm a big fan of reviewing databases using DB Browser for SQLite.

You should review all entries in the database, but often with Cache.db you'll find "blobs" in the database that are binary property lists (PLISTs).

Fig.6 - Binary PLIST blobs in database

A big hint that the blob you are seeing in the database is a binary PLIST is the bplist string at the top of the blob.

Fig.7 - bplist string

You could individually export these binary PLISTs and convert them for review, but there's a faster way to do this type of analysis. First we'll extract all the binary PLISTs and dump them to files.

Next, we will extract any embedded PLISTs. PLISTs can have additional PLISTs embedded in them, and we'll use a script to extract those additional binary PLISTs and save them to a file.

Fig.8 - Why not more PLISTs?

Finally, we'll convert the binary PLISTs to ASCII, so we can review them for sensitive data in a text editor.

In the directory where you have your recovered Cache.db file, create a subdirectory dataDump. This is the directory where our commands will save the extracted binary PLISTs.

The following command will dump the Cache.db database, search for binary PLISTs, and save each to a file in our dataDump directory.

COUNTER=1; for i in `sqlite3 Cache.db .dump | grep ",X'\|,x'"  | egrep -o "X\'[A-z0-9]*'" | awk -F"'" '{print $2}'`; do echo $i | xxd -r -p > dataDump/$((COUNTER)).dump.plist; COUNTER=$((COUNTER+1)); done

Now we want to extract any embedded PLISTs. We'll use plistsubstractor to do this. This awesome tool by Joshua Wright extracts from a binary PLIST any embedded PLISTs found in it, and saves them to another file. You can find the original python2 tool here, or a slightly modified python3 version here.

Move to the dataDump directory and run the following command, updating to wherever your plistsubstractor script is located.

for i in `ls *.dump.plist`; do python3 plistsubtractor3.py $i; done

Now you'll want to convert the binary PLISTs into ASCII for review. I use plutil on macOS to perform this task.

You can convert a binary PLIST to ASCII using the command:

plutil -convert xml1 plist.file

We can loop through the files in the dataDump directory searching for Apple binary property list files and convert them all at once:

for plist in `file * | grep 'Apple binary property list' | awk -F':' '{print $1}'`; do plutil -convert xml1 $plist; done

Then, open all of the PLIST files in your editor and start searching for session tokens, sensitive server responses, and credentials. You will be surprised by how much sensitive information you can find. Make sure you decode any base64 strings you find in the extracted PLISTs.

Remember, there are far more files in local application storage that can inadvertently store sensitive data beyond the Cache.db file. All files should be reviewed, however SQLite databases and PLIST files in particular tend to be the most likely candidates for sensitive data.

Cache.db almost always contains sensitive data, is created by default, and appears to also be backed up by default. I find this an odd design choice by Apple, who could easily keep the Cache.db file out of backups by default, or even encrypt the database and leverage the iOS Keychain for secure key storage.

Developers hoping to mitigate this vulnerability can look to disable backups for the Caches directory or disable caching completely. There are ideas on how to mitigate this issue listed in the References below.

If you have issues or ideas how this can be improved, or better methods of mitigations my DMs are always open @hoodoer or on Mastodon at @[email protected].