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:
-
No encryption: The credit card number and password are stored in plaintext, making them easily accessible to attackers.
-
Persistent storage: Once stored, the sensitive data remains on the device until explicitly removed, increasing the window of vulnerability.
-
iCloud synchronization: When using
NSUbiquitousKeyValueStore, sensitive data is synchronized to iCloud, expanding the attack surface to include cloud storage compromise. -
Backup exposure: Application preferences are typically included in device backups, potentially exposing sensitive data through backup files.
-
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:
-
Use Keychain for sensitive data: Always store passwords, tokens, keys, and other sensitive information in the Keychain.
-
Set appropriate access controls: Use
kSecAttrAccessibleWhenUnlockedThisDeviceOnlyto prevent data from being included in backups and to require device unlock. -
Minimize sensitive data storage: Only store sensitive information when absolutely necessary, and delete it as soon as it’s no longer needed.
-
Use data protection: Enable data protection for your app to ensure files are encrypted when the device is locked.
-
Avoid iCloud sync for sensitive data: Never use
NSUbiquitousKeyValueStorefor 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
-
CWE-312: Cleartext Storage of Sensitive Information.
-
CWE-359: Exposure of Private Personal Information to an Unauthorized Actor.
-
OWASP Top 10 2021 A04 - Insecure Design.
-
MASWE-0006: Sensitive Data Stored Unencrypted in Private Storage Locations.
-
MASWE-0007: Sensitive Data Stored Unencrypted in Shared Storage Requiring No User Interaction.