Skip to content

Storage

Where Android apps store data, how to extract it, and what tends to become a real finding during a pentest.


Why Storage Matters

A lot of mobile findings are not about transport security or fancy runtime hooks. They are about data at rest:

  • Session tokens left in SharedPreferences
  • Refresh tokens or API keys stored in plaintext files
  • SQLite databases holding PII or payment data
  • WebView cookies and local storage surviving logout
  • Sensitive exports cached to shared storage where any app can read them

Storage review answers a simple question: if I get filesystem access, partial app access, backup access, or another app on the same device, what secrets fall out immediately?


Storage Map

Most app data lives under the app sandbox:

/data/data/com.example.app/
    shared_prefs/
    databases/
    files/
    cache/
    app_webview/
    no_backup/

Typical locations:

Location What it usually holds What to look for
shared_prefs/ XML key-value pairs auth tokens, feature flags, email, device IDs
databases/ SQLite / Room DBs PII, messages, offline records, cached API data
files/ Arbitrary app files exported PDFs, JSON blobs, certs, serialized objects
cache/ Temporary files API responses, images, report exports, unencrypted temp data
app_webview/ Chromium WebView state cookies, local storage, IndexedDB, autofill artifacts
no_backup/ Files excluded from Android backup long-lived keys, install IDs, migration markers
External storage Files outside the sandbox world-readable exports, logs, backups

Getting Access to App Data

Choose the least intrusive method that works.

1. run-as on a Debuggable App

If the manifest has android:debuggable="true", you can enter the sandbox without root:

adb shell
run-as com.example.app

pwd
ls
cd shared_prefs
cat auth.xml

This is the cleanest path on test builds.

2. Rooted Device or Rooted Emulator

On a rooted device or a Google APIs emulator with adb root:

adb root
adb shell
cd /data/data/com.example.app/
ls

Or copy the whole sandbox out:

adb root
adb pull /data/data/com.example.app ./com.example.app_dump

3. Backup-Based Extraction

On older apps and devices, backup paths may still work if backup is allowed. This is less reliable on modern Android, but it is still worth checking when allowBackup is enabled or the app targets older APIs.

See the backup discussion under App Structure for the exact workflow.

4. Runtime Access with Frida

If direct filesystem access is limited, read data through the app's own APIs:

  • SharedPreferences via getSharedPreferences()
  • SQLite wrappers or DAOs via hooked methods
  • WebView cookies via CookieManager
  • Keystore usage via crypto helper hooks

Use this when you have code execution in-process but not root.


SharedPreferences

SharedPreferences are simple XML files stored in:

/data/data/com.example.app/shared_prefs/

Example:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="access_token">eyJhbGciOi...</string>
    <string name="refresh_token">def50200...</string>
    <string name="user_email">victim@example.com</string>
    <boolean name="is_logged_in" value="true" />
</map>

What to Look For

  • Access tokens, refresh tokens, JWTs
  • API keys and environment URLs
  • Role or feature flags like isAdmin, isPremium, debug_enabled
  • Cached credentials or PINs
  • Encryption keys stored next to encrypted data

Recon

adb shell run-as com.example.app ls shared_prefs
adb shell run-as com.example.app cat shared_prefs/auth.xml

# Rooted path
adb shell cat /data/data/com.example.app/shared_prefs/auth.xml

If the app uses EncryptedSharedPreferences, note that this is better than plaintext but still not magic. The real question becomes: where is the master key derived from, and can the app itself decrypt values for you at runtime?


SQLite Databases

Android apps commonly use SQLite directly or through Room. Database files live in:

/data/data/com.example.app/databases/

Typical contents:

  • User profiles and account metadata
  • Chat messages and notifications
  • Offline sync queues
  • Cached API responses
  • Payment or order records
  • Device binding and session state

Basic Workflow

# List databases
adb shell run-as com.example.app ls databases

# Copy one out
adb shell run-as com.example.app cp databases/app.db /sdcard/app.db
adb pull /sdcard/app.db

# Inspect locally
sqlite3 app.db ".tables"
sqlite3 app.db ".schema"
sqlite3 app.db "select * from users limit 10;"

High-Value Tables

Look for table names containing:

  • user, account, profile
  • session, token, auth
  • message, chat, notification
  • payment, card, order, invoice
  • config, feature_flag, remote_config

Common Findings

  • Sensitive records stored unencrypted
  • SQL injection in app-managed providers or raw query builders
  • Deleted data still recoverable from cache tables or WAL files
  • Privileged flags editable client-side and trusted by the UI

Also pull the SQLite sidecar files if they exist:

adb shell run-as com.example.app ls databases
# look for: app.db-wal and app.db-shm

The WAL often contains recently written rows not yet merged into the main DB.


Internal Files and Cache

Not everything is in SharedPreferences or SQLite. Apps often dump raw blobs into files/ or cache/.

Common examples:

  • Exported PDFs and CSVs
  • Downloaded API responses
  • Serialized Java objects
  • Temporary image uploads
  • Crash artifacts with stack traces or request bodies
  • Cached GraphQL or REST responses

Recon

adb shell run-as com.example.app find files -maxdepth 3 -type f
adb shell run-as com.example.app find cache -maxdepth 3 -type f

# Rooted alternative
adb shell find /data/data/com.example.app/files -maxdepth 3 -type f
adb shell find /data/data/com.example.app/cache -maxdepth 3 -type f

Pull anything that looks interesting:

adb shell run-as com.example.app cp files/session.json /sdcard/session.json
adb pull /sdcard/session.json

What to Look For

  • Plaintext JSON containing tokens or user records
  • Reports exported to cache before share intents
  • Files with misleading extensions that are really ZIPs, SQLite DBs, or protobuf blobs
  • Temp files created before encryption or before upload

External and Shared Storage

Anything written outside the app sandbox is immediately more exposed. Historically this meant SD card paths; on modern Android it includes app-specific external dirs plus media collections.

Examples:

/sdcard/
/sdcard/Download/
/sdcard/Android/data/com.example.app/
/storage/emulated/0/

Why It Matters

  • Other apps may be able to read it, depending on Android version and granted permissions
  • Users often back it up or sync it without realising it contains secrets
  • Pentesters can often pull it without root

Recon

adb shell ls /sdcard/
adb shell find /sdcard/Android/data/com.example.app -maxdepth 4 -type f
adb shell find /sdcard/Download -maxdepth 2 -type f | grep -i "example\|report\|export"

Common Findings

  • Auth tokens in exported config files
  • Logs written to world-readable paths
  • Unencrypted backups or offline cache files
  • Temporary camera or document uploads left behind after completion

On Android 10+ scoped storage limits broad file access, but apps can still leak their own sensitive data by writing it to shared locations.


WebView Storage

If the app uses WebView, Chromium leaves its own storage under app_webview/.

Interesting artifacts include:

  • Cookies
  • Local Storage
  • IndexedDB
  • Autofill state

Typical paths:

/data/data/com.example.app/app_webview/
/data/data/com.example.app/app_webview/Cookies
/data/data/com.example.app/app_webview/Default/Local Storage/
/data/data/com.example.app/app_webview/Default/IndexedDB/

What to Look For

  • Session cookies surviving logout
  • OAuth or SSO tokens stored in browser state
  • Sensitive profile data cached in local storage
  • Internal admin endpoints visible in cached web content

Runtime Access Example

Java.perform(function () {
    var CookieManager = Java.use("android.webkit.CookieManager");
    var cm = CookieManager.getInstance();
    console.log(cm.getCookie("https://app.example.com"));
});

That is often faster than pulling and parsing the SQLite cookie DB by hand.


Android Keystore

The Android Keystore protects key material better than raw files, but it does not automatically make the overall design secure.

Questions to ask:

  • Is the app encrypting sensitive data, or just a few fields?
  • Where does the ciphertext live?
  • Can the app decrypt it for any logged-in user session without extra checks?
  • Is the same keystore key reused across users or environments?

What Usually Goes Wrong

  • The app stores ciphertext securely but leaves the decrypted value in cache or logs
  • The app stores the keystore alias and encrypted blob together, then exposes a method that decrypts on demand
  • A rooted or instrumented attacker can call the app's decrypt method directly

So when you see Keystore usage in jadx, do not stop at "encrypted". Trace the full encrypt → store → load → decrypt flow.


Logs and Crash Artifacts

Storage review should include anything the app writes for debugging or support.

Look for:

  • Local log files in files/, cache/, or external storage
  • Stack traces including tokens, request bodies, or SQL queries
  • Crash reporters caching unsent reports
  • Screenshot or screen-recording features that dump sensitive content

Basic recon:

adb shell run-as com.example.app find . -type f | grep -Ei "log|crash|report|trace|dump"
adb logcat | grep -i "token\|auth\|password\|exception"

If the app integrates a crash SDK, search for its cache path in jadx and inspect what it stores locally before upload.


Quick Storage Checklist

[ ] Check shared_prefs/ for tokens, flags, PII, URLs, keys
[ ] Check databases/ plus -wal / -shm files
[ ] Enumerate files/ and cache/ for JSON, exports, temp blobs
[ ] Check external storage paths for app-owned files
[ ] If WebView is used, inspect app_webview/ state
[ ] Trace any encryption scheme end-to-end; do not stop at "uses Keystore"
[ ] Search for logs, crash files, screenshots, and support bundles
[ ] If direct filesystem access is blocked, read data through Frida hooks

For a new target:

1. Check manifest flags: debuggable, allowBackup, exported providers
2. Get filesystem access: run-as, root, or backup path
3. Read SharedPreferences first
4. Dump and inspect SQLite databases and WAL files
5. Enumerate files/, cache/, no_backup/
6. Check external storage and app_webview/
7. Trace any encryption or Keystore use in jadx / Frida
8. Validate whether stored values can be replayed or tampered with