OWASP Mobile Top 10 — M9

Insecure Data Storage

Mobile applications frequently store sensitive data insecurely on the device, leaving it exposed to attackers who gain physical access, deploy malware, or perform forensic analysis. From plaintext credentials in UserDefaults to unencrypted databases, insecure data storage remains one of the most common and dangerous mobile security vulnerabilities.

What Is Insecure Data Storage?

Insecure Data Storage occurs when a mobile application stores sensitive information such as authentication tokens, passwords, personal identifiable information (PII), or financial data in locations that are not adequately protected. On iOS, this commonly manifests as storing secrets in UserDefaults (which writes to a plaintext plist file), unencrypted SQLite or Core Data databases, application log files, or the system pasteboard. These storage locations are easily accessible to anyone with physical access to the device, through iTunes or iCloud backups, or via another compromised application on a jailbroken device.

Mobile devices are particularly vulnerable to insecure data storage because they are easily lost or stolen, frequently backed up to cloud services, and often shared among family members. Unlike server-side applications where the data environment can be tightly controlled, mobile apps operate on devices that the developer has no control over. Attackers can use freely available forensic tools to extract data from device backups, examine the application sandbox on jailbroken devices, or intercept data written to shared storage locations like the pasteboard or keyboard cache.

A critical aspect of this vulnerability is that sensitive data can persist on the device even after an application has been uninstalled. SQLite databases may leave residual data in write-ahead log (WAL) files, cached HTTP responses can linger in the system cache directory, and screenshots captured by the OS when the app is backgrounded can expose sensitive screens. This means that even if a user deletes an application, their sensitive data may remain recoverable through forensic analysis of the device storage.

How It Works

1
App stores sensitive data on the device

The application collects or receives sensitive information such as API tokens, user credentials, session identifiers, or personal data. The developer decides to persist this data locally for convenience, offline access, or to avoid repeated authentication.

2
Data is written to unprotected storage locations

Instead of using secure storage mechanisms like the iOS Keychain, the developer writes sensitive data to easily accessible locations such as UserDefaults, plist files, unencrypted SQLite databases, or Core Data stores without encryption. These locations store data in plaintext and are included in device backups.

3
Attacker gains access to the device or its data

An attacker obtains access through physical theft of the device, extracting data from an unencrypted iTunes or iCloud backup, exploiting a vulnerability in another app on a jailbroken device, or using forensic analysis tools on a device that has been discarded or resold without a proper factory reset.

4
Attacker reads sensitive data from predictable locations

Using well-known file paths within the application sandbox, the attacker locates and reads the sensitive data. UserDefaults plist files are stored at Library/Preferences/, SQLite databases at Documents/ or Library/, and cached data in Library/Caches/. These paths are consistent and well-documented.

5
Stolen credentials or tokens are used to compromise accounts

With the extracted API tokens, session identifiers, or plaintext credentials, the attacker impersonates the user, accesses their backend account, exfiltrates additional personal data, or performs unauthorized transactions. The user may remain unaware of the compromise for an extended period.

Vulnerable Code Example

Vulnerable — Swift / iOS
import Foundation

class AuthManager {

    func saveUserCredentials(username: String, password: String) {
        // VULNERABLE: Storing credentials in UserDefaults
        // UserDefaults writes to a plaintext plist file
        // accessible via device backups and forensic tools
        UserDefaults.standard.set(username, forKey: "username")
        UserDefaults.standard.set(password, forKey: "password")
    }

    func saveApiToken(_ token: String) {
        // VULNERABLE: API token stored in plaintext plist file
        let paths = FileManager.default.urls(
            for: .documentDirectory, in: .userDomainMask
        )
        let filePath = paths[0].appendingPathComponent("config.plist")

        let data: [String: Any] = [
            "apiToken": token,
            "refreshToken": UserDefaults.standard.string(
                forKey: "refreshToken"
            ) ?? ""
        ]
        let plistData = try? PropertyListSerialization.data(
            fromPropertyList: data,
            format: .xml,
            options: 0
        )
        try? plistData?.write(to: filePath)
    }

    func loadCredentials() -> (String?, String?) {
        let username = UserDefaults.standard.string(forKey: "username")
        let password = UserDefaults.standard.string(forKey: "password")
        return (username, password)
    }
}

// An attacker who gains access to the device backup can
// read the plist file at:
// Library/Preferences/com.app.bundle.plist
// and find plaintext username, password, and API tokens.
// Tools like iExplorer or libimobiledevice make this trivial.

Secure Code Example

Secure — Swift / iOS (Keychain Services)
import Foundation
import Security

class SecureAuthManager {

    func saveToKeychain(key: String, value: String) -> Bool {
        guard let data = value.data(using: .utf8) else {
            return false
        }

        // Delete any existing item first
        let deleteQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key
        ]
        SecItemDelete(deleteQuery as CFDictionary)

        // SECURE: Store in iOS Keychain with strong protection
        // Data is encrypted, excluded from backups, and
        // only accessible when the device is unlocked
        let addQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String:
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]

        let status = SecItemAdd(addQuery as CFDictionary, nil)
        return status == errSecSuccess
    }

    func loadFromKeychain(key: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var result: AnyObject?
        let status = SecItemCopyMatching(
            query as CFDictionary, &result
        )

        guard status == errSecSuccess,
              let data = result as? Data,
              let value = String(data: data, encoding: .utf8)
        else {
            return nil
        }
        return value
    }

    func saveCredentials(username: String, password: String) {
        _ = saveToKeychain(key: "auth_username", value: username)
        _ = saveToKeychain(key: "auth_password", value: password)
    }

    func saveApiToken(_ token: String) {
        _ = saveToKeychain(key: "api_token", value: token)
    }
}

// With Keychain Services, credentials are encrypted by the
// Secure Enclave, excluded from device backups, and only
// accessible when the device is unlocked by the owner.
// Even with physical access, an attacker cannot extract
// the stored secrets without the device passcode.

Types of Insecure Data Storage

Plaintext Storage

Credentials, API tokens, and session identifiers stored in UserDefaults, NSUserDefaults, or plist files without any encryption. UserDefaults persists data as an XML plist in the application's Library/Preferences/ directory, which is included in device backups and trivially readable with forensic tools. This is the most common form of insecure storage, often introduced by developers who treat UserDefaults as a general-purpose secrets store rather than a preferences mechanism for non-sensitive data.

Unencrypted Databases

SQLite and Core Data stores without encryption expose structured data including user profiles, transaction records, and cached API responses. The default SQLite database used by Core Data stores all data in plaintext and can be opened with any SQLite browser. Without encryption via SQLCipher or similar libraries, anyone with access to the device filesystem or a backup can query the entire database and extract all stored records, including sensitive fields that developers may assume are protected by the app sandbox.

Cache & Log Leakage

Sensitive data inadvertently captured in HTTP response caches, keyboard autocomplete caches, screenshot caches (captured by iOS when the app enters the background), or application logs via NSLog or print statements. Developers often log API responses or user data during debugging and forget to remove these statements in production builds. The iOS keyboard cache stores typed words for autocomplete, potentially capturing passwords and credit card numbers unless the input field explicitly disables autocorrection.

Impact

Insecure data storage can have severe consequences for both users and organizations. The impact depends on the type of data exposed, the number of affected users, and the attacker's objectives.

Credential theft and account takeover

Plaintext passwords, API tokens, or session identifiers stored on the device can be extracted and used to take over user accounts. Attackers can impersonate the user, change account settings, or lock the legitimate user out entirely. This is especially damaging when users reuse credentials across multiple services.

Exposure of personal identifiable information (PII)

Names, email addresses, phone numbers, physical addresses, and government identification numbers stored insecurely can be harvested for identity theft, social engineering attacks, or sold on dark web marketplaces. PII exposure can have long-lasting consequences for affected individuals that extend far beyond the initial breach.

Financial data theft

Payment card details, bank account numbers, transaction histories, and cryptocurrency wallet keys stored in plaintext on the device can be directly exploited for financial fraud. Attackers can initiate unauthorized transactions, clone payment instruments, or drain accounts before the user realizes the device has been compromised.

Regulatory violations (GDPR, HIPAA, PCI DSS)

Insecure storage of personal, health, or financial data can result in violations of data protection regulations. GDPR mandates appropriate technical measures for data protection, HIPAA requires encryption of protected health information, and PCI DSS prohibits storing sensitive authentication data. Non-compliance can result in substantial fines, mandatory breach notifications, and lasting reputational damage.

Prevention Checklist

Store sensitive data in the iOS Keychain with appropriate protection levels

The iOS Keychain is the designated secure storage mechanism for secrets on Apple platforms. Use Keychain Services with the kSecAttrAccessibleWhenUnlockedThisDeviceOnly accessibility flag to ensure data is encrypted, excluded from backups, and only decryptable when the device is unlocked. For highly sensitive data, add biometric protection using SecAccessControl with .biometryCurrentSet.

Encrypt local databases using SQLCipher or similar encryption

If your application uses SQLite or Core Data to store sensitive information locally, encrypt the database at rest using SQLCipher or a similar transparent encryption library. This ensures that even if the database file is extracted from the device, the contents remain unreadable without the encryption key, which should itself be stored in the Keychain.

Avoid storing sensitive data in UserDefaults or plist files

UserDefaults is designed for non-sensitive application preferences like theme settings, onboarding state, or feature flags. Never store passwords, tokens, API keys, or personal data in UserDefaults. Audit your codebase for any instances of sensitive data being written to UserDefaults and migrate them to the Keychain.

Disable keyboard caching for sensitive input fields

The iOS keyboard caches typed words to improve autocomplete suggestions. For sensitive input fields such as passwords, credit card numbers, or SSNs, set autocorrectionType = .no and isSecureTextEntry = true on UITextField to prevent the keyboard from caching input. Also consider disabling the pasteboard for these fields to prevent clipboard leakage.

Clear sensitive data from memory when no longer needed

Overwrite sensitive data in memory as soon as it is no longer required. Use mutable data types that can be zeroed out rather than immutable String objects. Implement applicationDidEnterBackground to clear sensitive data from views and variables, and avoid keeping decrypted secrets in memory longer than necessary to reduce the window of exposure.

Implement data protection APIs (NSFileProtectionComplete)

Apply iOS Data Protection to files that must be stored on disk by setting the NSFileProtectionComplete attribute. This ensures files are encrypted with a key derived from the user's passcode and are completely inaccessible when the device is locked. For files that need background access, use NSFileProtectionCompleteUnlessOpen as a compromise between security and functionality.

Real-World Examples

2018

Strava Fitness App

Strava's fitness tracking app was found to store detailed GPS location data and running routes in an unencrypted SQLite database on the device. Security researchers demonstrated that this data could reveal the locations of military bases and secret facilities when soldiers used the app during exercise. The incident exposed sensitive government and military operational patterns through data that was trivially extractable from the device.

2019

Several Banking Apps (AppSec Research)

A security audit by researchers at the University of Birmingham found that multiple major banking apps on iOS and Android stored authentication tokens, account numbers, and transaction data in plaintext within the application sandbox. Some apps cached full API responses including account balances and card details in unencrypted SQLite databases, making them accessible through device backups without requiring a jailbreak.

2020

COVID-19 Contact Tracing Apps

Security researchers found that several government-deployed COVID-19 contact tracing applications stored health status data, location histories, and personal identifiers in plaintext on the device. In some cases, the apps logged Bluetooth encounter data to unencrypted files that persisted even after the app was uninstalled, raising significant privacy concerns about health data exposure and potential discrimination against infected individuals.

2021

WhatsApp Message Database

Despite WhatsApp's end-to-end encryption for messages in transit, security researchers repeatedly demonstrated that message databases stored locally on the device were accessible in plaintext (on Android) or through iCloud backups (on iOS). This highlighted a fundamental gap where strong transport encryption was undermined by insecure local storage, allowing forensic tools to extract complete chat histories, media files, and contact information from device backups.

Ready to Test Your Knowledge?

Put what you have learned into practice. Try identifying and fixing insecure data storage vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.