Unveiling Strategies to Identify Potentially Sensitive Data Stored by iOS Applications

ADIP
10 min readApr 27, 2024

--

Overview

Let’s focuses on identifying potentially sensitive data stored by an application and verifying if it is securely stored. The following checks should be performed:

  • Analyze data storage in the source code.
  • Be sure to trigger all possible functionality in the application (e.g. by clicking everywhere possible) in order to ensure data generation.
  • Check all application generated and modified files and ensure that the storage method is sufficiently secure.
  • This includes NSUserDefaults, databases, KeyChain, Internal Storage, External Storage, etc.

NOTE: it is sufficient to store data unencrypted in the application’s internal storage directory (sandbox). additional encryption is required using cryptographic keys securely managed in the iOS KeyChain. This includes using envelope encryption (DEK+KEK) or equivalent methods.

Static Analysis

When you have access to the source code of an iOS app, identify sensitive data that’s saved and processed throughout the app. This includes passwords, secret keys, and personally identifiable information (PII), but it may as well include other data identified as sensitive by industry regulations, laws, and company policies. Look for this data being saved via any of the local storage APIs listed below.

Make sure that sensitive data is never stored without appropriate protection. For example, authentication tokens should not be saved in NSUserDefaults without additional encryption. Also avoid storing encryption keys in .plist files, hardcoded as strings in code, or generated using a predictable obfuscation function or key derivation function based on stable attributes.

Sensitive data should be stored by using the Keychain API (that stores them inside the Secure Enclave), or stored encrypted using envelope encryption. Envelope encryption, or key wrapping, is a cryptographic construct that uses symmetric encryption to encapsulate key material. Data encryption keys (DEK) can be encrypted with key encryption keys (KEK) which must be securely stored in the Keychain. Encrypted DEK can be stored in NSUserDefaults or written in files. When required, application reads KEK, then decrypts DEK.

Keychain

The encryption must be implemented so that the secret key is stored in the Keychain with secure settings, ideally kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. This ensures the usage of hardware-backed storage mechanisms. Make sure that the AccessControlFlags are set according to the security policy of the keys in the KeyChain.

Generic examples of using the KeyChain ↗ to store, update, and delete data can be found in the official Apple documentation. The official Apple documentation also includes an example of using Touch ID and passcode protected keys ↗.

Filesystem

Using the source code, examine the different APIs used to store data locally. Make sure that any data is properly encrypted based on its sensitivity.

Dynamic Analysis

One way to determine whether sensitive information (like credentials and keys) is stored insecurely without leveraging native iOS functions is to analyze the app’s data directory. Triggering all app functionality before the data is analyzed is important because the app may store sensitive data only after specific functionality has been triggered. You can then perform static analysis for the data dump according to generic keywords and app-specific data.

The following steps can be used to determine how the application stores data locally on a jailbroken iOS device:

  1. Trigger the functionality that stores potentially sensitive data.
  2. Connect to the iOS device and navigate to its Bundle directory (this applies to iOS versions 8.0 and above): /var/mobile/Containers/Data/Application/$APP_ID/
  3. Execute grep with the data that you’ve stored, for example: grep -iRn "USERID".
  4. If the sensitive data is stored in plaintext, the app fails this test.

You can analyze the app’s data directory on a non-jailbroken iOS device by using third-party applications, such as iMazing ↗.

  1. Trigger the functionality that stores potentially sensitive data.
  2. Connect the iOS device to your host computer and launch iMazing.
  3. Select “Apps”, right-click the desired iOS application, and select “Extract App”.
  4. Navigate to the output directory and locate $APP_NAME.imazing. Rename it to $APP_NAME.zip.
  5. Unpack the ZIP file. You can then analyze the application data.

Note that tools like iMazing don’t copy data directly from the device. They try to extract data from the backups they create. Therefore, getting all the app data that’s stored on the iOS device is impossible: not all folders are included in backups. Use a jailbroken device or repackage the app with Frida and use a tool like objection to access all the data and files.

you can use objection ↗ to transfer files directly from the app’s data directory or read files in objection

The Keychain contents can be dumped during dynamic analysis. On a jailbroken device, you can use Keychain dumper ↗ as described in the chapter “Basic Security Testing on iOS”.

The path to the Keychain file is

/private/var/Keychains/keychain-2.db

On a non-jailbroken device, you can use objection to dump the Keychain items ↗ created and stored by the app.

Dynamic Analysis with Xcode and iOS simulator

This test is only available on macOS, as Xcode and the iOS simulator is needed.

For testing the local storage and verifying what data is stored within it, it’s not mandatory to have an iOS device. With access to the source code and Xcode the app can be build and deployed in the iOS simulator. The file system of the current device of the iOS simulator is available in ~/Library/Developer/CoreSimulator/Devices.

Once the app is running in the iOS simulator, you can navigate to the directory of the latest simulator started with the following command:

$ cd ~/Library/Developer/CoreSimulator/Devices/$( ls -alht ~/Library/Developer/CoreSimulator/Devices | head -n 2 | awk '{print $9}' | sed -n '1!p')/data/Containers/Data/Application

The command above will automatically find the UUID of the latest simulator started. Now you still need to grep for your app name or a keyword in your app. This will show you the UUID of the app.

grep -iRn keyword .

Then you can monitor and verify the changes in the filesystem of the app and investigate if any sensitive information is stored within the files while using the app.

Dynamic Analysis with Objection

You can use the objection ↗ runtime mobile exploration toolkit to find vulnerabilities caused by the application’s data storage mechanism. Objection can be used without a Jailbroken device, but it will require patching the iOS Application ↗.

Reading the Keychain

To use Objection to read the Keychain, execute the following command:

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios keychain dump
Note: You may be asked to authenticate using the devices passcode or TouchID
Save the output by adding `--json keychain.json` to this command
Dumping the iOS keychain...
Created Accessible ACL Type Account Service Data
------------------------- ------------------------------ ----- -------- ------------------------- ------------------------------------------------------------- ------------------------------------
2020-02-11 13:26:52 +0000 WhenUnlocked None Password keychainValue com.highaltitudehacks.DVIAswiftv2.develop mysecretpass123

Searching for Binary Cookies

iOS applications often store binary cookie files in the application sandbox. Cookies are binary files containing cookie data for application WebViews. You can use objection to convert these files to a JSON format and inspect the data.

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios cookies get --json
[
{
"domain": "highaltitudehacks.com",
"expiresDate": "2051-09-15 07:46:43 +0000",
"isHTTPOnly": "false",
"isSecure": "false",
"name": "username",
"path": "/",
"value": "admin123",
"version": "0"
}
]

Searching for Property List Files

iOS applications often store data in property list (plist) files that are stored in both the application sandbox and the IPA package. Sometimes these files contain sensitive information, such as usernames and passwords; therefore, the contents of these files should be inspected during iOS assessments. Use the ios plist cat plistFileName.plist command to inspect the plist file.

To find the file userInfo.plist, use the env command. It will print out the locations of the applications Library, Caches and Documents directories:

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # env
Name Path
----------------- -------------------------------------------------------------------------------------------
BundlePath /private/var/containers/Bundle/Application/B2C8E457-1F0C-4DB1-8C39-04ACBFFEE7C8/DVIA-v2.app
CachesDirectory /var/mobile/Containers/Data/Application/264C23B8-07B5-4B5D-8701-C020C301C151/Library/Caches
DocumentDirectory /var/mobile/Containers/Data/Application/264C23B8-07B5-4B5D-8701-C020C301C151/Documents
LibraryDirectory /var/mobile/Containers/Data/Application/264C23B8-07B5-4B5D-8701-C020C301C151/Library

Go to the Documents directory and list all files using ls.

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ls
NSFileType Perms NSFileProtection Read Write Owner Group Size Creation Name
------------ ------- ------------------------------------ ------ ------- ------------ ------------ -------- ------------------------- ------------------------
Directory 493 n/a True True mobile (501) mobile (501) 192.0 B 2020-02-12 07:03:51 +0000 default.realm.management
Regular 420 CompleteUntilFirstUserAuthentication True True mobile (501) mobile (501) 16.0 KiB 2020-02-12 07:03:51 +0000 default.realm
Regular 420 CompleteUntilFirstUserAuthentication True True mobile (501) mobile (501) 1.2 KiB 2020-02-12 07:03:51 +0000 default.realm.lock
Regular 420 CompleteUntilFirstUserAuthentication True True mobile (501) mobile (501) 284.0 B 2020-05-29 18:15:23 +0000 userInfo.plist
Unknown 384 n/a True True mobile (501) mobile (501) 0.0 B 2020-02-12 07:03:51 +0000 default.realm.note

Readable: True Writable: True

Execute the ios plist cat command to inspect the content of userInfo.plist file.

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios plist cat userInfo.plist
{
password = password123;
username = userName;
}

Searching for SQLite Databases

iOS applications typically use SQLite databases to store data required by the application. Testers should check the data protection values of these files and their contents for sensitive data. Objection contains a module to interact with SQLite databases. It allows to dump the schema, their tables and query the records.

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # sqlite connect Model.sqlite
Caching local copy of database file...
Downloading /var/mobile/Containers/Data/Application/264C23B8-07B5-4B5D-8701-C020C301C151/Library/Application Support/Model.sqlite to /var/folders/4m/dsg0mq_17g39g473z0996r7m0000gq/T/tmpdr_7rvxi.sqlite
Streaming file from device...
Writing bytes to destination...
Successfully downloaded /var/mobile/Containers/Data/Application/264C23B8-07B5-4B5D-8701-C020C301C151/Library/Application Support/Model.sqlite to /var/folders/4m/dsg0mq_17g39g473z0996r7m0000gq/T/tmpdr_7rvxi.sqlite
Validating SQLite database format
Connected to SQLite database at: Model.sqlite

SQLite @ Model.sqlite > .tables
+--------------+
| name |
+--------------+
| ZUSER |
| Z_METADATA |
| Z_MODELCACHE |
| Z_PRIMARYKEY |
+--------------+
Time: 0.013s

SQLite @ Model.sqlite > select * from Z_PRIMARYKEY
+-------+--------+---------+-------+
| Z_ENT | Z_NAME | Z_SUPER | Z_MAX |
+-------+--------+---------+-------+
| 1 | User | 0 | 0 |
+-------+--------+---------+-------+
1 row in set
Time: 0.013s

Searching for Cache Databases

By default NSURLSession stores data, such as HTTP requests and responses in the Cache.db database. This database can contain sensitive data, if tokens, usernames or any other sensitive information has been cached. To find the cached information open the data directory of the app (/var/mobile/Containers/Data/Application/<UUID>) and go to /Library/Caches/<Bundle Identifier>. The WebKit cache is also being stored in the Cache.db file. Objection can open and interact with the database with the command sqlite connect Cache.db, as it is a normal SQLite database.

It is recommended to disable Caching this data, as it may contain sensitive information in the request or response. The following list below shows different ways of achieving this:

  1. It is recommended to remove Cached responses after logout. This can be done with the provided method by Apple called removeAllCachedResponses You can call this method as follows:

URLCache.shared.removeAllCachedResponses()

This method will remove all cached requests and responses from Cache.db file.

  1. If you don’t need to use the advantage of cookies it would be recommended to just use the .ephemeral ↗ configuration property of URLSession, which will disable saving cookies and Caches.
  2. Apple documentation ↗:
  3. An ephemeral session configuration object is similar to a default session configuration (see default), except that the corresponding session object doesn’t store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file.
  4. Cache can be also disabled by setting the Cache Policy to .notAllowed ↗. It will disable storing Cache in any fashion, either in memory or on disk.

Checking Logs for Sensitive Data

Static Analysis

Use the following keywords to check the app’s source code for predefined and custom logging statements:

  • For predefined and built-in functions:
  • NSLog
  • NSAssert
  • NSCAssert
  • fprintf

For custom functions:

  • Logging
  • Logfile

A generalized approach to this issue is to use a define to enable NSLog statements for development and debugging, then disable them before shipping the software. You can do this by adding the following code to the appropriate PREFIX_HEADER (*.pch) file:

#ifdef DEBUG
# define NSLog (...) NSLog(__VA_ARGS__)
#else
# define NSLog (...)
#endif

Determining Whether Sensitive Data Is Shared with Third Parties:

Sensitive information might be leaked to third parties by several means. On iOS typically via third-party services embedded in the app.

The features these services provide can involve tracking services to monitor the user’s behavior while using the app, selling banner advertisements, or improving the user experience.

The downside is that developers don’t usually know the details of the code executed via third-party libraries. Consequently, no more information than is necessary should be sent to a service, and no sensitive information should be disclosed.

Most third-party services are implemented in two ways:

  • with a standalone library
  • with a full SDK

Static Analysis

To determine whether API calls and functions provided by the third-party library are used according to best practices, review their source code, requested permissions and check for any known vulnerabilities.

All data that’s sent to third-party services should be anonymized to prevent exposure of PII (Personal Identifiable Information) that would allow the third party to identify the user account. No other data (such as IDs that can be mapped to a user account or session) should be sent to a third party.

Dynamic Analysis

Check all requests to external services for embedded sensitive information. To intercept traffic between the client and server, you can perform dynamic analysis by launching a man-in-the-middle (MITM) attack with Burp Suite Professional or OWASP ZAP. Once you route the traffic through the interception proxy, you can try to sniff the traffic that passes between the app and server. All app requests that aren’t sent directly to the server on which the main function is hosted should be checked for sensitive information, such as PII in a tracker or ad service.

Delving into the Keyboard Cache for Sensitive Data:

Static Analysis

  • Search through the source code for similar implementations, such as
 textObject.autocorrectionType = UITextAutocorrectionTypeNo;
textObject.secureTextEntry = YES;
  • Open xib and storyboard files in the Interface Builder of Xcode and verify the states of Secure Text Entry and Correction in the Attributes Inspector for the appropriate object.

The application must prevent the caching of sensitive information entered into text fields. You can prevent caching by disabling it programmatically, using the textObject.autocorrectionType = UITextAutocorrectionTypeNo directive in the desired UITextFields, UITextViews, and UISearchBars. For data that should be masked, such as PINs and passwords, set textObject.secureTextEntry to YES.

UITextField *textField = [ [ UITextField alloc ] initWithFrame: frame ]; textField.autocorrectionType = UITextAutocorrectionTypeNo;

Dynamic Analysis

If a jailbroken iPhone is available, execute the following steps:

  1. Reset your iOS device keyboard cache by navigating to Settings > General > Reset > Reset Keyboard Dictionary.
  2. Use the application and identify the functionalities that allow users to enter sensitive data.
  3. Dump the keyboard cache file with the extension .dat in the following directory and its subdirectories. (which might be different for iOS versions before 8.0): /private/var/mobile/Library/Keyboard/
  4. Look for sensitive data, such as username, passwords, email addresses, and credit card numbers. If the sensitive data can be obtained via the keyboard cache file, the app fails this test.
UITextField *textField = [ [ UITextField alloc ] initWithFrame: frame ]; textField.autocorrectionType = UITextAutocorrectionTypeNo;

If you must use a non-jailbroken iPhone:

  1. Reset the keyboard cache.
  2. Key in all sensitive data.
  3. Use the app again and determine whether autocorrect suggests previously entered sensitive information.

--

--