๐ What Is It?
Insecure Data Storage occurs when mobile applications store sensitive data in unprotected locations on the device. This includes storing data in plain text in SharedPreferences, NSUserDefaults, SQLite databases, log files, temporary caches, or external storage. Mobile devices can be lost, stolen, or compromised, making secure data storage essential.
M09
OWASP Rank
86%
Apps Affected
<20min
Time to Extract
โ ๏ธ Common Exploits
- Physical Access: Access data on lost/stolen device
- Backup Extraction: Retrieve data from device backups
- Malware Access: Read app data directories
- Root/Jailbreak: Access all app sandboxes on compromised device
- Log File Analysis: Extract sensitive data from logs
- Cache Inspection: Recover data from temporary storage
๐ด Attack Flow
1. Attacker obtains physical device access
โ
2. Connects device via USB, enables root
โ
3. Browses /data/data/com.app/shared_prefs/
โ
4. Finds auth_token.xml with plaintext token
โ
5. BREACH: Full account access!
โ
2. Connects device via USB, enables root
โ
3. Browses /data/data/com.app/shared_prefs/
โ
4. Finds auth_token.xml with plaintext token
โ
5. BREACH: Full account access!
โ Vulnerable Code
// Bad: Plain text in SharedPreferences (Android)
public void saveUserData(String username, String password, String token) {
SharedPreferences prefs = getSharedPreferences("UserData", MODE_PRIVATE);
// VULNERABLE: Storing sensitive data in plain text
prefs.edit()
.putString("username", username)
.putString("password", password) // Never store passwords!
.putString("auth_token", token)
.putString("credit_card", "4532-1234-5678-9010")
.apply();
}
// Bad: Unencrypted SQLite (Android)
public void saveToDatabase(String ssn, String dob) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// VULNERABLE: Sensitive data in plain SQLite
ContentValues values = new ContentValues();
values.put("ssn", ssn);
values.put("date_of_birth", dob);
values.put("medical_record", "...");
db.insert("users", null, values);
}
// Bad: External storage (Android)
public void saveFile(String data) {
// VULNERABLE: World-readable external storage
File file = new File(Environment.getExternalStorageDirectory(),
"userdata.txt");
FileWriter writer = new FileWriter(file);
writer.write(data); // Any app can read this!
}
// Bad: NSUserDefaults plain text (iOS)
func saveCredentials(username: String, password: String) {
let defaults = UserDefaults.standard
// VULNERABLE: Plain text storage
defaults.set(username, forKey: "username")
defaults.set(password, forKey: "password")
defaults.set(authToken, forKey: "token")
defaults.synchronize()
}
// Bad: Temporary files left behind (iOS)
func processImage(_ image: UIImage) {
let temp = NSTemporaryDirectory() + "sensitive.jpg"
// VULNERABLE: Sensitive data in temp, not cleaned up
try? image.jpegData(compressionQuality: 1.0)?
.write(to: URL(fileURLWithPath: temp))
// File persists after app closes!
}
// Bad: Logging sensitive data
Log.d("Auth", "User password: " + password); // VULNERABLE!
NSLog("Credit card: %@", creditCard); // VULNERABLE!
โ Secure Code
// Good: Encrypted SharedPreferences (Android)
public void saveUserDataSecurely(String username, String token) {
try {
// Use EncryptedSharedPreferences (androidx.security)
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// Data encrypted automatically
prefs.edit()
.putString("username", username)
.putString("auth_token", token) // Encrypted
.apply();
} catch (Exception e) {
Log.e("Security", "Encryption error", e);
}
}
// Good: SQLCipher for encrypted database (Android)
public void saveToEncryptedDatabase(String ssn, String dob) {
// Use SQLCipher for encrypted SQLite
SQLiteDatabase.loadLibs(context);
String password = generateDatabaseKey(); // From Android Keystore
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
getDatabasePath("secure.db"),
password,
null
);
ContentValues values = new ContentValues();
values.put("ssn", ssn);
values.put("dob", dob);
db.insert("users", null, values);
}
// Good: Internal storage only (Android)
public void saveFileSecurely(String data) {
// Use internal storage (app-private)
File file = new File(getFilesDir(), "userdata.txt");
// Encrypt before writing
String encrypted = encryptData(data);
try (FileWriter writer = new FileWriter(file)) {
writer.write(encrypted);
}
}
// Good: iOS Keychain (iOS)
func saveCredentialsSecurely(username: String, password: String) {
// Use Keychain for credentials
let passwordData = password.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecValueData as String: passwordData,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
// Delete old entry if exists
SecItemDelete(query as CFDictionary)
// Add to Keychain
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
print("Keychain save failed: \(status)")
}
}
// Good: Secure file operations (iOS)
func saveDataSecurely(_ data: Data) {
let fileManager = FileManager.default
guard let documentsURL = fileManager.urls(
for: .documentDirectory,
in: .userDomainMask
).first else { return }
let fileURL = documentsURL.appendingPathComponent("secure.dat")
// Encrypt data before writing
if let encryptedData = encrypt(data) {
try? encryptedData.write(
to: fileURL,
options: [.completeFileProtection] // Device-level encryption
)
}
}
// Good: Clean up temporary files
func processImageSecurely(_ image: UIImage) {
let tempURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString + ".jpg")
defer {
// Always clean up temp files
try? FileManager.default.removeItem(at: tempURL)
}
if let data = image.jpegData(compressionQuality: 0.8) {
try? data.write(to: tempURL)
processFile(tempURL)
}
}
// Good: Never log sensitive data
if (BuildConfig.DEBUG) {
Log.d("Auth", "Login attempt for user"); // No PII
}
// Production: no logging of credentials/tokens at all
โ Prevention Checklist
- Use EncryptedSharedPreferences (Android) for sensitive data
- Store credentials in iOS Keychain or Android Keystore
- Encrypt SQLite databases with SQLCipher
- Never store sensitive data on external storage
- Use internal app storage with encryption
- Clean up temporary files after use
- Remove sensitive data from logs
- Exclude sensitive files from backups
- Implement data-at-rest encryption
- Regular security audits of data storage
๐ Detection & Tools
Analysis Tools:
MobSF
Objection
Frida
adb
iExplorer
SQLite Browser
Encryption Libraries:
androidx.security
SQLCipher
Keychain Services
Tink
Conceal
How to Test:
- Extract APK and check /data/data/[app]/
- Examine SharedPreferences XML files for plain text
- Open SQLite databases to verify encryption
- Check external storage for sensitive files
- Extract and analyze device backups
- Review logcat output for sensitive data
๐ Real-World Breaches
- Numerous Apps (2020): OAuth tokens stored unencrypted in SharedPreferences
- Health App (2019): Medical records in plain SQLite database
- Banking Apps (2018): PINs stored in NSUserDefaults on iOS
- Social Media (2020): Auth tokens in plaintext logs accessible via backup
- E-commerce (2019): Payment card data in unencrypted local storage
๐ Quick Tips
- DO NOT store passwords on device
- DO NOT use plain text for sensitive data
- DO NOT write sensitive data to external storage
- DO use platform secure storage (Keychain/Keystore)
- DO encrypt all sensitive data at rest
- DO clean up temporary files properly
๐ Compliance
Related Standards:
- PCI-DSS Requirement 3.4, 8.2.1
- GDPR Art. 32 - Encryption at Rest
- HIPAA ยง164.312(a)(2)(iv)
- NIST 800-53 SC-28
- OWASP MASVS MSTG-STORAGE-1 to 15
- ISO 27001 A.10.1.1