Cleartext storage of sensitive information in an application preference store

ID

swift.cleartext_storage_preferences

Severity

high

Resource

Information leak

Language

Swift

Tags

CWE:312, CWE:359, MASWE:0006, MASWE:0007, NIST.SP.800-53, OWASP:2021:A4, PCI-DSS:3.4, PCI-DSS:6.5.3

Description

Sensitive information that is stored unencrypted in an application preference store (such as UserDefaults or NSUbiquitousKeyValueStore) is accessible to an attacker who gains access to that data store. This can occur through physical access to a rooted/jailbroken device, through compromise via app extensions, or through other vulnerabilities that allow reading application data.

Application preference stores like UserDefaults are designed for storing small amounts of user preferences and configuration data, not for storing sensitive information. These stores are:

  • Not encrypted by default

  • Stored in plaintext on the device’s file system

  • Accessible to attackers who gain file system access

  • Synchronized via iCloud if using NSUbiquitousKeyValueStore, potentially exposing data through cloud account compromise

Rationale

The following is an example of vulnerable code that stores a credit card number in UserDefaults:

import Foundation

func saveCreditCard(creditCardNo: String) {
    // VULNERABLE: Storing sensitive data in cleartext
    UserDefaults.standard.set(creditCardNo, forKey: "creditCardNumber")
}

func saveUserPassword(password: String) {
    // VULNERABLE: Storing password in iCloud in cleartext
    NSUbiquitousKeyValueStore.default.set(password, forKey: "userPassword")
}

This code has several security problems:

  1. No encryption: The credit card number and password are stored in plaintext, making them easily accessible to attackers.

  2. Persistent storage: Once stored, the sensitive data remains on the device until explicitly removed, increasing the window of vulnerability.

  3. iCloud synchronization: When using NSUbiquitousKeyValueStore, sensitive data is synchronized to iCloud, expanding the attack surface to include cloud storage compromise.

  4. Backup exposure: Application preferences are typically included in device backups, potentially exposing sensitive data through backup files.

  5. Shared container access: On iOS, app extensions and the main app share access to UserDefaults, meaning a vulnerability in any extension could expose the sensitive data.

Remediation

Instead of storing sensitive information in plaintext, use secure storage mechanisms designed for sensitive data:

Option 1: Use the Keychain (Recommended)

The Keychain is specifically designed for storing sensitive information securely. It provides encryption and access control:

import Security
import Foundation

class SecureStorage {
    // Store sensitive data in Keychain
    func saveCreditCard(creditCardNo: String) -> Bool {
        guard let data = creditCardNo.data(using: .utf8) else {
            return false
        }

        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: "myCreditCardNo",
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]

        SecItemDelete(query as CFDictionary) // Remove any existing item
        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }

    // Retrieve sensitive data from Keychain
    func getCreditCard() -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: "myCreditCardNo",
            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 creditCard = String(data: data, encoding: .utf8) else {
            return nil
        }

        return creditCard
    }
}

Option 2: Encrypt before storing (Alternative)

If you must use UserDefaults for some reason, encrypt the sensitive data before storage:

import Foundation
import CryptoKit

class EncryptedStorage {
    private let encryptionKey: SymmetricKey

    init() {
        // In production, derive this key securely or store it in Keychain
        self.encryptionKey = SymmetricKey(size: .bits256)
    }

    func saveCreditCard(creditCardNo: String) throws {
        guard let data = creditCardNo.data(using: .utf8) else {
            throw EncryptionError.invalidData
        }

        // Encrypt the sensitive data
        let sealedBox = try AES.GCM.seal(data, using: encryptionKey)
        guard let encryptedData = sealedBox.combined else {
            throw EncryptionError.encryptionFailed
        }

        // Store the encrypted data
        UserDefaults.standard.set(encryptedData, forKey: "myCreditCardNo_encrypted")
    }

    func getCreditCard() throws -> String {
        guard let encryptedData = UserDefaults.standard.data(forKey: "myCreditCardNo_encrypted") else {
            throw EncryptionError.notFound
        }

        // Decrypt the data
        let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
        let decryptedData = try AES.GCM.open(sealedBox, using: encryptionKey)

        guard let creditCardNo = String(data: decryptedData, encoding: .utf8) else {
            throw EncryptionError.invalidData
        }

        return creditCardNo
    }

    enum EncryptionError: Error {
        case invalidData
        case encryptionFailed
        case notFound
    }
}

Best Practices:

  1. Use Keychain for sensitive data: Always store passwords, tokens, keys, and other sensitive information in the Keychain.

  2. Set appropriate access controls: Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly to prevent data from being included in backups and to require device unlock.

  3. Minimize sensitive data storage: Only store sensitive information when absolutely necessary, and delete it as soon as it’s no longer needed.

  4. Use data protection: Enable data protection for your app to ensure files are encrypted when the device is locked.

  5. Avoid iCloud sync for sensitive data: Never use NSUbiquitousKeyValueStore for sensitive information, unless properly encrypted. Encryption keys should be managed securely, which is not an easy feat.

Configuration

This detector can be configured with the list of sensitive data kinds to detect:

sensitiveKinds:
- access_control
- crypto
- financial
- health
- location
- personal_identifiable_information

If you consider that some kind of sensitive data does not need encryption in storage (location for example), you can remove it from the list.

References