Data Storage Security Testing Methodology
Data storage testing focuses on identifying sensitive data stored insecurely on the device, including databases, preferences, files, and cache.
Storage Locations
Internal Storage
/data/data/<package>/ - App's private directory
/data/data/<package>/shared_prefs/ - SharedPreferences XML files
/data/data/<package>/databases/ - SQLite databases
/data/data/<package>/files/ - Application files
/data/data/<package>/cache/ - Cache directory
External Storage
/sdcard/Android/data/<package>/ - App-specific external storage
/sdcard/ - General external storage (world-readable)
SharedPreferences Testing
Extraction
# Using MCP tool
prefs = dump_shared_prefs("com.target.app")
Manual Analysis
# List preference files
adb shell "su -c 'ls -la /data/data/com.target.app/shared_prefs/'"
# Read preference files
adb shell "su -c 'cat /data/data/com.target.app/shared_prefs/*.xml'"
# Search for sensitive data
adb shell "su -c 'grep -riE \"password|token|key|secret|session\" /data/data/com.target.app/shared_prefs/'"
What to Look For
| Data Type |
Risk Level |
MASTG Reference |
| Passwords in plaintext |
Critical |
MASTG-TEST-0001 |
| Authentication tokens |
High |
MASTG-TEST-0001 |
| Session IDs |
High |
MASTG-TEST-0001 |
| API keys |
High |
MASTG-TEST-0001 |
| Encryption keys |
Critical |
MASTG-TEST-0001 |
| PII (email, phone, SSN) |
Medium |
MASTG-TEST-0002 |
| Financial data |
High |
MASTG-TEST-0002 |
| PIN codes/hashes |
High |
MASTG-TEST-0001 |
Runtime Monitoring
frida_run_script(pid, """
Java.perform(function() {
var SharedPrefsEditor = Java.use('android.app.SharedPreferencesImpl$EditorImpl');
SharedPrefsEditor.putString.implementation = function(key, value) {
console.log('[PREFS] putString: ' + key + ' = ' + value);
return this.putString(key, value);
};
SharedPrefsEditor.commit.implementation = function() {
console.log('[PREFS] commit() called');
return this.commit();
};
SharedPrefsEditor.apply.implementation = function() {
console.log('[PREFS] apply() called');
return this.apply();
};
});
""")
SQLite Database Testing
Extraction
# Using MCP tool
databases = dump_databases("com.target.app")
Manual Analysis
# List databases
adb shell "su -c 'ls -la /data/data/com.target.app/databases/'"
# Pull database
adb shell "su -c 'cp /data/data/com.target.app/databases/*.db /sdcard/'"
adb pull /sdcard/*.db ./databases/
# Analyze with sqlite3
sqlite3 database.db ".tables"
sqlite3 database.db ".schema"
sqlite3 database.db "SELECT * FROM users;"
sqlite3 database.db "SELECT * FROM credentials;"
Encrypted Database Detection
# Check file signature
file database.db
# SQLite header: "SQLite format 3"
# SQLCipher: encrypted, no readable header
# If encrypted, hook to capture password
Decrypting SQLCipher
frida_run_script(pid, """
Java.perform(function() {
// Hook SQLCipher
var SQLiteDatabase = Java.use('net.sqlcipher.database.SQLiteDatabase');
SQLiteDatabase.openOrCreateDatabase.overload('java.lang.String', 'java.lang.String', 'net.sqlcipher.database.SQLiteDatabase$CursorFactory').implementation = function(path, password, factory) {
console.log('[DB] SQLCipher database opened');
console.log(' Path: ' + path);
console.log(' Password: ' + password);
return this.openOrCreateDatabase(path, password, factory);
};
SQLiteDatabase.openDatabase.overload('java.lang.String', 'java.lang.String', 'net.sqlcipher.database.SQLiteDatabase$CursorFactory', 'int').implementation = function(path, password, factory, flags) {
console.log('[DB] SQLCipher database opened');
console.log(' Path: ' + path);
console.log(' Password: ' + password);
return this.openDatabase(path, password, factory, flags);
};
});
""")
SQL Injection in Database Queries
frida_run_script(pid, """
Java.perform(function() {
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
// Hook rawQuery - vulnerable to injection
SQLiteDatabase.rawQuery.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(sql, selectionArgs) {
console.log('[SQL] rawQuery: ' + sql);
if (selectionArgs) {
console.log(' Args: ' + JSON.stringify(selectionArgs));
}
return this.rawQuery(sql, selectionArgs);
};
// Hook execSQL - direct SQL execution
SQLiteDatabase.execSQL.overload('java.lang.String').implementation = function(sql) {
console.log('[SQL] execSQL: ' + sql);
return this.execSQL(sql);
};
});
""")
File Storage Testing
Internal Files
# Using MCP tool
files = dump_internal_storage("com.target.app")
External Files
# Using MCP tool
external = dump_external_storage("com.target.app")
Manual Analysis
# List all files in app directory
adb shell "su -c 'ls -laR /data/data/com.target.app/'"
# Find sensitive files
adb shell "su -c 'find /data/data/com.target.app/ -name \"*.json\" -o -name \"*.xml\" -o -name \"*.pem\" -o -name \"*.key\" -o -name \"*.p12\"'"
# Check external storage
adb shell "su -c 'ls -laR /sdcard/Android/data/com.target.app/'"
# World-readable files
adb shell "su -c 'find /sdcard/ -name \"*com.target.app*\"'"
File Permission Checks
# Check permissions
adb shell "su -c 'ls -la /data/data/com.target.app/files/'"
# Vulnerable: MODE_WORLD_READABLE (deprecated but still used)
# rw-rw-rw- indicates world-readable
Runtime File Monitoring
frida_run_script(pid, """
Java.perform(function() {
var FileOutputStream = Java.use('java.io.FileOutputStream');
FileOutputStream.$init.overload('java.io.File').implementation = function(file) {
console.log('[FILE] Write: ' + file.getAbsolutePath());
return this.$init(file);
};
FileOutputStream.$init.overload('java.lang.String').implementation = function(path) {
console.log('[FILE] Write: ' + path);
return this.$init(path);
};
var FileInputStream = Java.use('java.io.FileInputStream');
FileInputStream.$init.overload('java.io.File').implementation = function(file) {
console.log('[FILE] Read: ' + file.getAbsolutePath());
return this.$init(file);
};
});
""")
Android Keystore Testing
Keystore Usage Detection
# Search for Keystore usage in code
grep -rniE "(KeyStore|KeyGenerator|KeyPairGenerator)" jadx_output/ --include="*.java"
Keystore Monitoring
frida_run_script(pid, """
Java.perform(function() {
var KeyStore = Java.use('java.security.KeyStore');
KeyStore.getInstance.overload('java.lang.String').implementation = function(type) {
console.log('[KEYSTORE] getInstance: ' + type);
return this.getInstance(type);
};
KeyStore.getKey.implementation = function(alias, password) {
console.log('[KEYSTORE] getKey: ' + alias);
var key = this.getKey(alias, password);
if (key) {
console.log(' Algorithm: ' + key.getAlgorithm());
}
return key;
};
KeyStore.setKeyEntry.overload('java.lang.String', 'java.security.Key', '[C', '[Ljava.security.cert.Certificate;').implementation = function(alias, key, password, chain) {
console.log('[KEYSTORE] setKeyEntry: ' + alias);
console.log(' Algorithm: ' + key.getAlgorithm());
return this.setKeyEntry(alias, key, password, chain);
};
// KeyGenParameterSpec for hardware-backed keys
var KeyGenParameterSpec = Java.use('android.security.keystore.KeyGenParameterSpec$Builder');
KeyGenParameterSpec.$init.overload('java.lang.String', 'int').implementation = function(keystoreAlias, purposes) {
console.log('[KEYSTORE] KeyGenParameterSpec: ' + keystoreAlias);
console.log(' Purposes: ' + purposes);
return this.$init(keystoreAlias, purposes);
};
});
""")
Keystore Security Issues
| Issue |
Description |
Impact |
| No user authentication |
Key usable without device unlock |
High |
| No biometric binding |
Key not protected by biometrics |
Medium |
| Weak key properties |
Short validity, weak algorithm |
Medium |
| Key exportable |
Key can be extracted |
High |
Backup Testing
Check Backup Flag
# In AndroidManifest.xml
grep "allowBackup" AndroidManifest.xml
Extract via Backup
# Create backup (if allowBackup=true)
adb backup -f backup.ab com.target.app
# Convert to tar
java -jar abe.jar unpack backup.ab backup.tar
# Extract
tar -xvf backup.tar
# Analyze extracted data
ls -laR apps/com.target.app/
Logcat Analysis
Extract Logs
# Using MCP tool
logs = get_logcat("com.target.app")
Search for Sensitive Data
# Real-time log monitoring
adb logcat | grep -i "com.target.app"
# Search for sensitive data
adb logcat -d | grep -iE "password|token|key|secret|session|bearer"
# Application-specific logs
adb logcat -d --pid=$(adb shell pidof com.target.app)
Disable Logging in Production
Search for logging calls that should be removed:
grep -rniE "Log\.(d|v|i|w|e)\(" jadx_output/ --include="*.java"
Data Storage Checklist
SharedPreferences
Databases
Files
Keystore
Logging
Backup
Common Findings
| Finding |
Severity |
MASTG Reference |
| Credentials in SharedPreferences |
Critical |
MASTG-TEST-0001 |
| Unencrypted database |
High |
MASTG-TEST-0001 |
| Tokens in plaintext files |
High |
MASTG-TEST-0001 |
| Sensitive data on external storage |
High |
MASTG-TEST-0003 |
| Debug logging with secrets |
Medium |
MASTG-TEST-0004 |
| Backup enabled with sensitive data |
High |
MASTG-TEST-0005 |
| World-readable files |
High |
MASTG-TEST-0002 |
| No Keystore for secrets |
Medium |
MASTG-TEST-0006 |