Skip to main content

HTML Caching

Almost every iOS app communicates to a web server to perform transactions such as authentication, or data retrieval. Apple makes setting up these connections fairly easy for the Developer by providing the NSURLRequest API to handle HTTP requests. Unfortunately, this API will cache the HTTP requests and responses by default leaving them on the device.

Cache.db

This cache of HTTP communications can usually be found in the applications' DATA directory which is typically ${DATA}/Library/Caches/appID/Cache.db. The "appID" directory is typically the bundle ID of the app (i.e., com.company.appname). The Cache.db file is a standard SQLite database. There may also be Cache.db-wal and Cache.db-shm files alongside the main database file. These are the "Write Ahead Log" and "Shared Memory" files that are used in SQLite and can safely be ignored for our purposes.

The Cache.db is usually located in the following directory:

${DATADIR}/Library/Caches/${BUNDLEID}/Cache.db

As mentioned, the Cache.db file is a standard SQLite database that contains 4 tables of data. For our purposes, only 3 of these tables are of interest. By using the '.tables' command in sqlite3 we can get a list of the tables in the database.

$ sqlite3 Cache.db
SQLite version 3.12.1 2016-04-08 15:09:49
Enter ".help" for usage hints.
sqlite> .tables
cfurl_cache_blob_data cfurl_cache_response
cfurl_cache_receiver_data cfurl_cache_schema_version

The first table is called "cfurl_cache_blob_data", and as the name implies this table holds Binary Large OBject (BLOB) data containing the response from the server. As the name suggests, we will not be able to view this data in the sqlite3 command. We could run strings on it to see much of the data, but there is a better way. This BLOB data turns out to be simple Property List (PLIST) files which are common on iOS devices. So, we can easily export the BLOB data from the database, and then use the plutil command to view the data.

The next table is "cfurl_cache_response", and contains the data requested and the response in standard text making it easy to review. There is also a field in this table called "isDataOnFs". If this column has a "1", then this information (request/response) is also stored in another folder on the iOS device within the Caches folder of the app. Typically, this directory is called fsCachedData and can be reviewed with the strings command.

The last table is "cfurl_cache_receiver_data", and this contains data received from the server in response to the cfurl_cache_response. This is also simple text data so it is easy to review.

Database Export

The sqlite3 command on the iOS device is a great tool to perform simple actions on the database. However, it is not sufficient to properly dump the BLOB table from the database. So, the best thing is to copy the Cache.db from the device to your Mac to fully dump the database out to text files.

The "cfurl_cache_response" and "cfurl_cache_receiver_data" tables are easy to dump from the database:

sqlite3 $1 "SELECT * FROM cfurl_cache_response" >> cache_response.txt

sqlite3 $1 "SELECT * FROM cfurl_cache_receiver_data" >> cache_receiver_data.txt

The "cfurl_cache_blob_data" is a bit more complicated to export. Each column of each row contains BLOB data. So, we have to dump each one individually, and write them to a text file. So, to start we must determine how many rows the table has, and then loop through each row to dump the data.

To find out how many rows the table has, and assigning it to a variable for the loop, can be done as follows:

ROWS=$(sqlite3 Cache.db "SELECT COUNT(*) FROM cfurl_cache_blob_data")

Then we run the loop to dump the data out of each column in each row:

for i in $(seq 1 ${ROWS})
do
sqlite3 $1 "SELECT writefile('${TMPDIR}/response_data_$i.txt', response_object) FROM cfurl_cache_blob_data WHERE entry_ID = '$i'" >> /dev/null

sqlite3 $1 "SELECT writefile('${TMPDIR}/request_data_$i.txt', request_object) FROM cfurl_cache_blob_data WHERE entry_ID = '$i'" >> /dev/null

sqlite3 $1 "SELECT writefile('${TMPDIR}/proto_props_$i.txt', proto_props) FROM cfurl_cache_blob_data WHERE entry_ID = '$i'" >> /dev/null
done

At this point we have the data dumped out, but it is still in binary mode. So, we have to do a bit more processing of this data to make it plain text. This can be done by using the plutil utility to echo the output into a text file.

for i in `ls response_data_*`
do
plutil -p $i >> response_data_all.txt
done

for i in `ls request_data_*`
do
plutil -p $i >> request_data_all.txt
done

for i in `ls proto_props_*`
do
plutil -p $i >> proto_props_all.txt
done

Now we have all of the data that is housed in the Cache.db SQLite database dumped out to simple text files. The tedious part starts now to identify what data is being cached by the application and see if it is sensitive.

fsCachedData

Depending on how the app is setup, there may also be a directory called "fsCachedData" which contains several binary files of the HTTP cache. This is usually located in the directory below:

${DATADIR}/Library/Caches/${BUNDLEID}/fsCachedData/filename.bin

However, the developer could set this directory up in other places in the ${DATADIR} folder. So, I would suggest searching the entire ${DATADIR} for it.

cd ${DATADIR}
find . -iname fscacheddata

Since the files are binary, use the strings command to review them.

NSURLCache

There could also be an nsurlcache directory which could also hold HTTP cache files. Search for and examine this directory if it exists.

cd ${DATADIR}
find . -iname nsurlcache