// FINDING / MOBILE

Insecure local storage

HighMobile

The app writes sensitive data (session tokens, personal data, cached API responses, credentials) to shared preferences, plist files, SQLite databases or logs without protection. It can be read from backups, from a rooted or jailbroken device, or by other apps through misconfigured storage.

At a glance

Why it matters
A stolen or shared device, a malicious app or an unencrypted backup exposes the user's session and data. Long-lived tokens found on the device let an attacker act as the user from anywhere until they expire.
How to fix it
Store secrets only in the iOS Keychain or the Android Keystore-backed storage, keep as little data on the device as possible and clear it on logout. Exclude sensitive files from backups, keep tokens short-lived and never log personal data.

Description

Insecure local storage is sensitive data (passwords, session tokens, personally identifiable information) written unencrypted to a mobile device's shared preferences, an on-device database, plain files, or left unnecessarily in memory or application logs. It differs from hardcoded secrets in that the data was collected at runtime, not shipped with the app, but the exposure is similar: anything stored in plain form on the device is available to anyone with access to that device, whether through physical access, a rooted or jailbroken device, or another app abusing shared storage.

Impact

A rooted or malware-infected phone, or an unlocked one in the wrong hands, exposes whatever an app stored in plain form: device encryption protects a locked phone at rest, but it does not protect data an app wrote out in plain form once the device is unlocked or compromised. This typically enables account takeover (a stored session token used directly), credential theft (a cached password), or exposure of personal data the app was trusted to protect, entirely independent of any vulnerability in the backend the app talks to.

How we find it

  • We inspect the app's shared preferences, local databases and files (through a rooted test device or emulator) for sensitive values stored in plain text.
  • We review what the app writes to logs during normal use and error conditions, since verbose logging of request or response bodies is a common way sensitive data ends up readable without even needing file-system access.
  • We check whether the platform's secure storage facilities (Android Keystore, iOS Keychain) are used for anything that should be encrypted at rest, and whether encryption keys, when used, are themselves protected rather than stored alongside the data they encrypt.

Fix

Store sensitive data only in the platform's dedicated secure storage (Android Keystore-backed encrypted storage, iOS Keychain), never in plain shared preferences, a plain SQLite database, or a plain file. Avoid logging sensitive fields at any log level, and treat anything cached for offline use with the same care as data sent over the network.

// Vulnerable: token written to plain shared preferences
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", token).apply()

// Fixed: encrypted storage backed by the Android Keystore
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()
val encryptedPrefs = EncryptedSharedPreferences.create(
    context, "secure_prefs", masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
encryptedPrefs.edit().putString("auth_token", token).apply()

The Jetpack Security library used in this example has since been deprecated; new code can keep the key in the Android Keystore directly (for example through Tink), but the principle is the same: the key lives in hardware-backed storage, never next to the data.

References

  • OWASP Mobile Top 10: M9, Insecure Data Storage
  • CWE-312: Cleartext Storage of Sensitive Information
  • OWASP Mobile Application Security Testing Guide (MASTG)