mobile security

Exploiting Insecure Data Storage in Mobile

Identifies and exploits insecure local data storage vulnerabilities in Android and iOS mobile applications including unencrypted databases, world-readable files, insecure SharedPreferences, plaintext credential storage, and improper keychain/keystore usage. Use when performing mobile penetration testing focused on OWASP M9 (Insecure Data Storage) or assessing compliance with MASVS-STORAGE requirements. Activates for requests involving mobile data storage security, local storage exploitation, SharedPreferences analysis, or mobile data leakage assessment.

androiddata-storageiosmobile-securityowasp-mobilepenetration-testing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

Use this skill when:

  • Assessing whether mobile applications store sensitive data securely on the device filesystem
  • Testing for credential leakage through SharedPreferences, SQLite databases, or plists
  • Evaluating keychain/keystore implementation for proper access control attributes
  • Performing data-at-rest security assessment during mobile penetration tests

Do not use this skill on production user devices without authorization -- data extraction techniques require physical access or root/jailbreak privileges.

Prerequisites

  • Rooted Android device or emulator with ADB access
  • Jailbroken iOS device with SSH access or Objection-patched IPA
  • ADB (Android Debug Bridge) for Android filesystem access
  • SQLite3 CLI for database inspection
  • Frida/Objection for runtime data extraction
  • Target application installed and exercised (logged in, data cached)

Legal Notice: This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws.

Workflow

Step 1: Map Application Data Storage Locations

Android storage paths:

# Internal storage (app-private, requires root)
/data/data/<package_name>/
├── shared_prefs/      # SharedPreferences XML files
├── databases/         # SQLite databases
├── files/             # General files
├── cache/             # Cached data
├── lib/               # Native libraries
└── app_webview/       # WebView data
 
# External storage (world-readable on older Android)
/sdcard/Android/data/<package_name>/
 
# Check for world-readable files
adb shell run-as <package_name> ls -la /data/data/<package_name>/

iOS storage paths:

# App sandbox (accessible via SSH on jailbroken device)
/var/mobile/Containers/Data/Application/<UUID>/
├── Documents/         # User data, backed up by default
├── Library/
   ├── Preferences/   # NSUserDefaults plists
   ├── Caches/        # Cache data
   └── Application Support/
└── tmp/               # Temporary files

Step 2: Extract and Analyze SharedPreferences (Android)

# Pull SharedPreferences files
adb shell run-as <package_name> cat shared_prefs/*.xml
 
# Or on rooted device
adb pull /data/data/<package_name>/shared_prefs/ ./shared_prefs/
 
# Search for sensitive data
grep -ri "password\|token\|secret\|key\|session\|auth\|cookie" shared_prefs/

Common insecure storage patterns:

<!-- Plaintext credentials -->
<string name="user_password">mysecretpass123</string>
<string name="auth_token">eyJhbGciOiJIUzI1NiIs...</string>
<string name="api_key">sk-live-abc123def456</string>
 
<!-- Sensitive PII -->
<string name="user_ssn">123-45-6789</string>
<string name="credit_card">4111111111111111</string>

Step 3: Analyze SQLite Databases

# Pull databases
adb pull /data/data/<package_name>/databases/ ./databases/
 
# Open and inspect
sqlite3 databases/app.db
.tables
.schema users
SELECT * FROM users;
SELECT * FROM sessions;
SELECT * FROM tokens;
 
# Search all tables for sensitive columns
sqlite3 databases/app.db ".dump" | grep -i "password\|token\|secret\|credit"

Check for unencrypted SQLCipher databases:

# If database opens without password, it's unencrypted
sqlite3 databases/app.db "SELECT count(*) FROM sqlite_master;"
# Success = unencrypted (vulnerability)

Step 4: Inspect iOS Keychain Storage

# Using Objection
objection --gadget com.target.app explore
ios keychain dump
 
# Check protection class attributes
# kSecAttrAccessibleWhenUnlocked - OK for most data
# kSecAttrAccessibleAlways - VULNERABLE: accessible even when locked
# kSecAttrAccessibleAfterFirstUnlock - acceptable for background apps

Step 5: Assess External Storage and Backup Exposure

Android:

# Check if backup is enabled
aapt dump badging target.apk | grep -i "allowBackup"
# android:allowBackup="true" = vulnerability
 
# Extract backup data
adb backup -f backup.ab -apk <package_name>
java -jar abe.jar unpack backup.ab backup.tar
tar xvf backup.tar
# Inspect extracted data for sensitive information
 
# Check external storage
adb shell ls -la /sdcard/Android/data/<package_name>/

iOS:

# Check backup exclusion
# Files in Documents/ are backed up by default
# Check NSURLIsExcludedFromBackupKey attribute
objection --gadget com.target.app explore
ios plist cat Info.plist

Step 6: Runtime Memory Analysis

# Dump process memory for sensitive data
objection --gadget com.target.app explore
memory search "password" --string
memory search "BEGIN RSA PRIVATE KEY" --string
memory dump all /tmp/memdump/
 
# Android: Check for sensitive data in logs
adb logcat -d | grep -i "password\|token\|key\|secret"

Key Concepts

Term Definition
SharedPreferences Android key-value storage in XML format; often misused for storing credentials in plaintext
Keychain Services iOS secure credential storage backed by Secure Enclave hardware on modern devices
Android Keystore Hardware-backed cryptographic key storage on Android; keys cannot be extracted from the device
SQLCipher Transparent encryption extension for SQLite databases; prevents data extraction without password
Data Protection API iOS file-level encryption tied to device passcode; controlled via protection class attributes

Tools & Systems

  • ADB (Android Debug Bridge): Command-line tool for Android device interaction and filesystem access
  • Objection: Frida-powered runtime exploration for keychain dumping and memory inspection
  • SQLite3: Command-line interface for inspecting unencrypted SQLite databases
  • Android Backup Extractor (ABE): Tool for unpacking ADB backup files to inspect stored data
  • iExplorer: GUI tool for browsing iOS app sandbox filesystem

Common Pitfalls

  • Encrypted but key in code: Some apps encrypt databases but store the encryption key in SharedPreferences or hardcoded in the binary. Always check for key storage alongside encryption.
  • MODE_WORLD_READABLE deprecation: This flag was deprecated in API 17, but legacy apps may still use it, making SharedPreferences readable by other apps.
  • iOS backup scope: By default, all files in the Documents directory are included in iTunes/iCloud backups. Verify that sensitive files have the backup exclusion attribute set.
  • Clipboard exposure: Data copied to clipboard is accessible to all apps. Check if the app copies sensitive data (passwords, tokens) to the clipboard.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md2.6 KB

API Reference: Insecure Mobile Data Storage Detection

OWASP Mobile Top 10 — M9: Insecure Data Storage

Risk Areas

Storage Type Platform Risk
SharedPreferences Android HIGH (plaintext XML)
SQLite databases Both CRITICAL if unencrypted
Keychain (improper) iOS MEDIUM
External storage Android HIGH (world-readable)
Plist files iOS HIGH (plaintext)

Android Data Locations

App Private Storage

/data/data/<package>/shared_prefs/    # SharedPreferences XML
/data/data/<package>/databases/        # SQLite databases
/data/data/<package>/files/            # App files
/data/data/<package>/cache/            # Cache data

External Storage (World-Readable)

/sdcard/Android/data/<package>/

ADB Commands

Pull App Data

adb pull /data/data/com.target.app/ ./extracted/

List SharedPreferences

adb shell run-as com.target.app ls /data/data/com.target.app/shared_prefs/

Read SharedPreferences

adb shell run-as com.target.app cat shared_prefs/credentials.xml

SQLite Analysis

Python sqlite3

import sqlite3
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
for table in cursor.fetchall():
    cursor.execute(f"PRAGMA table_info({table[0]})")
    print(cursor.fetchall())

iOS Data Locations

App Sandbox

/var/mobile/Containers/Data/Application/<UUID>/
    Documents/
    Library/Preferences/     # NSUserDefaults (plist)
    Library/Caches/
    tmp/

Keychain

# Using keychain-dumper
./keychain-dumper -a

Frida Scripts for Data Storage Audit

Hook SharedPreferences (Android)

Java.perform(function() {
    var sp = Java.use("android.app.SharedPreferencesImpl$EditorImpl");
    sp.putString.implementation = function(key, value) {
        console.log("SharedPrefs PUT: " + key + " = " + value);
        return this.putString(key, value);
    };
});

Hook NSUserDefaults (iOS)

var NSUserDefaults = ObjC.classes.NSUserDefaults;
var orig = NSUserDefaults["- setObject:forKey:"];
Interceptor.attach(orig.implementation, {
    onEnter: function(args) {
        console.log("NSUserDefaults: " + ObjC.Object(args[3]) + " = " + ObjC.Object(args[2]));
    }
});

Secure Storage Alternatives

Platform Secure Method
Android EncryptedSharedPreferences, Android Keystore
iOS Keychain Services with kSecAttrAccessible
Both SQLCipher for encrypted databases
standards.md2.8 KB

Standards Reference: Insecure Data Storage in Mobile

OWASP Mobile Top 10 2024 Mapping

OWASP ID Risk Data Storage Relevance
M1 Improper Credential Usage Hardcoded credentials in SharedPreferences, plists, databases
M6 Inadequate Privacy Controls PII stored unencrypted, accessible via backup extraction
M8 Security Misconfiguration allowBackup=true, world-readable files, missing encryption
M9 Insecure Data Storage Primary focus: all local storage vulnerabilities
M10 Insufficient Cryptography Weak encryption of local databases, hardcoded keys

OWASP MASVS v2.0 - MASVS-STORAGE Controls

Control Description Test Method
MASVS-STORAGE-1 App securely stores sensitive data Inspect SharedPreferences, keychain, databases
MASVS-STORAGE-2 App prevents sensitive data leakage Check logs, clipboard, backups, screenshots

NIST SP 800-163 Rev 1 - Mobile App Vetting

  • Section 4.3.1: Data storage analysis for sensitive information at rest
  • Section 4.3.2: Verification of encryption implementation for stored data
  • Section 5.2: Data protection requirements for enterprise mobile apps

CWE Mappings

CWE ID Title Storage Type
CWE-312 Cleartext Storage of Sensitive Information SharedPreferences, plists, SQLite
CWE-316 Cleartext Storage in Memory Process memory, clipboard
CWE-359 Exposure of Private Personal Information PII in unencrypted databases
CWE-522 Insufficiently Protected Credentials Passwords in SharedPreferences
CWE-532 Information Exposure Through Log Files Sensitive data in logcat/syslog
CWE-921 Storage of Sensitive Data in Unprotected Mechanism External storage, world-readable
CWE-922 Insecure Storage of Sensitive Information General insecure storage

Android Keystore Best Practices

Practice Secure Insecure
Key storage Android Keystore (hardware-backed) Hardcoded in APK or SharedPreferences
Database encryption SQLCipher with Keystore-derived key Unencrypted SQLite
Shared Preferences EncryptedSharedPreferences (Jetpack) MODE_PRIVATE without encryption
File encryption AES-256-GCM with Keystore key Plaintext files in internal storage

iOS Data Protection Classes

Class When Accessible Use Case
NSFileProtectionComplete Only when unlocked Highly sensitive data
NSFileProtectionCompleteUnlessOpen While open/unlocked Files written in background
NSFileProtectionCompleteUntilFirstUserAuthentication After first unlock Background-accessible data
NSFileProtectionNone Always Non-sensitive cached data
workflows.md3.2 KB

Workflows: Exploiting Insecure Data Storage in Mobile

Workflow 1: Android Data Storage Assessment

[Install & exercise app] --> [Root/ADB access] --> [Extract internal storage]
                                                          |
                                       +------------------+------------------+
                                       |                  |                  |
                              [SharedPreferences]   [SQLite DBs]      [File system]
                              [Grep for secrets]    [Open & query]    [Check permissions]
                              [Check encryption]    [Check SQLCipher] [External storage]
                                       |                  |                  |
                                       +------------------+------------------+
                                                          |
                                                   [Backup extraction]
                                                   [ADB backup test]
                                                          |
                                                   [Memory analysis]
                                                   [Logcat review]
                                                          |
                                                   [Report findings]

Workflow 2: iOS Data Storage Assessment

[Install & exercise app] --> [Jailbreak/Objection] --> [Extract sandbox data]
                                                              |
                                           +------------------+------------------+
                                           |                  |                  |
                                    [Keychain dump]    [Plist analysis]   [SQLite DBs]
                                    [Protection class] [NSUserDefaults]   [Core Data]
                                    [Access control]   [Sensitive values] [Encryption check]
                                           |                  |                  |
                                           +------------------+------------------+
                                                              |
                                                    [Backup inclusion check]
                                                    [Memory string search]
                                                    [Clipboard monitoring]
                                                              |
                                                    [Report findings]

Decision Matrix: Data Storage Risk

Storage Mechanism Encrypted Access Restricted Backup Excluded Risk Level
SharedPreferences (plaintext) No App-only No CRITICAL
EncryptedSharedPreferences Yes App-only Depends LOW
SQLite (no SQLCipher) No App-only No HIGH
SQLCipher (key in code) Yes* App-only No MEDIUM
Android Keystore Yes Hardware-backed N/A LOW
iOS Keychain (kSecAttrAccessibleAlways) Yes Always accessible N/A MEDIUM
iOS Keychain (complete protection) Yes When unlocked only N/A LOW
External storage No World-readable N/A CRITICAL

Scripts 2

agent.py6.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for detecting insecure data storage in mobile applications."""

import argparse
import json
import os
import re
import subprocess
import sqlite3
from datetime import datetime, timezone


ANDROID_SENSITIVE_PATHS = [
    "/data/data/{package}/shared_prefs/",
    "/data/data/{package}/databases/",
    "/data/data/{package}/files/",
    "/data/data/{package}/cache/",
    "/sdcard/Android/data/{package}/",
]

_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')

SENSITIVE_PATTERNS = {
    "api_key": re.compile(r'["\']?api[_-]?key["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
    "token": re.compile(r'["\']?(?:access|auth|bearer)[_-]?token["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
    "password": re.compile(r'["\']?password["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
    "private_key": re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----'),
    "base64_cred": re.compile(r'["\']?(?:auth|credential)["\']?\s*[:=]\s*["\']([A-Za-z0-9+/=]{20,})', re.I),
}


def scan_shared_prefs(prefs_dir):
    """Scan Android SharedPreferences XML files for sensitive data."""
    findings = []
    if not os.path.isdir(prefs_dir):
        return findings
    for fname in os.listdir(prefs_dir):
        if not fname.endswith(".xml"):
            continue
        fpath = os.path.join(prefs_dir, fname)
        try:
            with open(fpath, "r", errors="replace") as f:
                content = f.read()
            for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                matches = pattern.findall(content)
                if matches:
                    findings.append({
                        "file": fpath,
                        "type": "shared_prefs",
                        "pattern": pattern_name,
                        "match_count": len(matches),
                        "severity": "HIGH",
                    })
        except PermissionError:
            pass
    return findings


def scan_sqlite_databases(db_dir):
    """Scan SQLite databases for unencrypted sensitive data."""
    findings = []
    if not os.path.isdir(db_dir):
        return findings
    for fname in os.listdir(db_dir):
        if not fname.endswith((".db", ".sqlite", ".sqlite3")):
            continue
        fpath = os.path.join(db_dir, fname)
        try:
            conn = sqlite3.connect(fpath)
            cursor = conn.cursor()
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
            tables = cursor.fetchall()
            for (table_name,) in tables:
                if not _SAFE_TABLE_RE.match(table_name):
                    continue
                cursor.execute(f"PRAGMA table_info([{table_name}])")
                columns = cursor.fetchall()
                sensitive_cols = []
                for col in columns:
                    col_name = col[1].lower()
                    for sf in ["password", "token", "secret", "key", "ssn", "credit"]:
                        if sf in col_name:
                            sensitive_cols.append(col[1])
                if sensitive_cols:
                    cursor.execute(f"SELECT COUNT(*) FROM [{table_name}]")
                    row_count = cursor.fetchone()[0]
                    findings.append({
                        "file": fpath,
                        "table": table_name,
                        "sensitive_columns": sensitive_cols,
                        "row_count": row_count,
                        "encrypted": False,
                        "severity": "CRITICAL",
                    })
            conn.close()
        except (sqlite3.Error, PermissionError):
            pass
    return findings


def scan_file_storage(files_dir):
    """Scan app file storage for sensitive data."""
    findings = []
    if not os.path.isdir(files_dir):
        return findings
    for root, _, files in os.walk(files_dir):
        for fname in files:
            fpath = os.path.join(root, fname)
            try:
                with open(fpath, "r", errors="replace") as f:
                    content = f.read(4096)
                for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                    if pattern.search(content):
                        findings.append({
                            "file": fpath,
                            "pattern": pattern_name,
                            "severity": "HIGH",
                        })
            except (PermissionError, UnicodeDecodeError):
                pass
    return findings


def adb_pull_app_data(package_name, output_dir):
    """Pull application data via ADB for analysis."""
    os.makedirs(output_dir, exist_ok=True)
    paths = [p.format(package=package_name) for p in ANDROID_SENSITIVE_PATHS]
    results = []
    for path in paths:
        try:
            subprocess.check_output(
                ["adb", "pull", path, output_dir],
                text=True, errors="replace", timeout=15
            )
            results.append({"path": path, "status": "pulled"})
        except subprocess.SubprocessError:
            results.append({"path": path, "status": "failed"})
    return results


def main():
    parser = argparse.ArgumentParser(
        description="Detect insecure data storage in mobile apps (authorized testing only)"
    )
    parser.add_argument("--scan-dir", help="Directory containing app data to scan")
    parser.add_argument("--package", help="Android package name for ADB pull")
    parser.add_argument("--pull-dir", default=os.environ.get("MOBILE_AUDIT_DIR", "/tmp/mobile_audit"))
    parser.add_argument("--output", "-o", help="Output JSON report")
    args = parser.parse_args()

    print("[*] Insecure Mobile Data Storage Detection Agent")
    report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}

    scan_dir = args.scan_dir or args.pull_dir
    if args.package:
        adb_pull_app_data(args.package, args.pull_dir)

    if os.path.isdir(scan_dir):
        report["findings"].extend(scan_shared_prefs(scan_dir))
        report["findings"].extend(scan_sqlite_databases(scan_dir))
        report["findings"].extend(scan_file_storage(scan_dir))

    critical = sum(1 for f in report["findings"] if f.get("severity") == "CRITICAL")
    report["risk_level"] = "CRITICAL" if critical else "HIGH" if report["findings"] else "LOW"
    print(f"[*] Findings: {len(report['findings'])} (CRITICAL: {critical})")

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[*] Report saved to {args.output}")
    else:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
process.py10.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Mobile Data Storage Security Scanner

Analyzes extracted mobile app data directories for insecure storage patterns.
Scans SharedPreferences, SQLite databases, plists, and files for sensitive data.

Usage:
    python process.py --data-dir ./extracted_app_data [--platform android|ios] [--output report.json]
"""

import argparse
import json
import os
import re
import sqlite3
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path


SENSITIVE_PATTERNS = {
    "password": re.compile(r"(?:password|passwd|pwd)\s*[:=]\s*['\"]?([^\s'\"<]+)", re.IGNORECASE),
    "api_key": re.compile(r"(?:api[_-]?key|apikey)\s*[:=]\s*['\"]?([a-zA-Z0-9_-]{16,})", re.IGNORECASE),
    "token": re.compile(r"(?:auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['\"]?([a-zA-Z0-9_.-]{16,})", re.IGNORECASE),
    "secret": re.compile(r"(?:secret|private[_-]?key)\s*[:=]\s*['\"]?([a-zA-Z0-9_/+=]{16,})", re.IGNORECASE),
    "jwt": re.compile(r"eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"),
    "email": re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"),
    "credit_card": re.compile(r"\b(?:4\d{3}|5[1-5]\d{2}|6011|3[47]\d)\d{8,12}\b"),
    "ssn": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
    "private_key": re.compile(r"BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY"),
}


def scan_shared_preferences(prefs_dir: str) -> list:
    """Scan Android SharedPreferences XML files for sensitive data."""
    findings = []
    prefs_path = Path(prefs_dir)

    if not prefs_path.exists():
        return findings

    for xml_file in prefs_path.glob("*.xml"):
        try:
            tree = ET.parse(xml_file)
            root = tree.getroot()

            for element in root.iter():
                name = element.get("name", "")
                value = element.text or element.get("value", "")

                for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                    if pattern.search(name) or pattern.search(str(value)):
                        findings.append({
                            "source": f"SharedPreferences/{xml_file.name}",
                            "key": name,
                            "value_preview": str(value)[:50] + "..." if len(str(value)) > 50 else str(value),
                            "pattern_matched": pattern_name,
                            "severity": "CRITICAL" if pattern_name in ("password", "private_key", "credit_card") else "HIGH",
                        })
        except ET.ParseError:
            findings.append({
                "source": f"SharedPreferences/{xml_file.name}",
                "key": "PARSE_ERROR",
                "pattern_matched": "error",
                "severity": "INFO",
            })

    return findings


def scan_sqlite_databases(db_dir: str) -> list:
    """Scan SQLite databases for sensitive data."""
    findings = []
    db_path = Path(db_dir)

    if not db_path.exists():
        return findings

    for db_file in list(db_path.glob("*.db")) + list(db_path.glob("*.sqlite")):
        try:
            conn = sqlite3.connect(str(db_file))
            cursor = conn.cursor()

            # Check if database is encrypted
            try:
                cursor.execute("SELECT count(*) FROM sqlite_master;")
                findings.append({
                    "source": f"Database/{db_file.name}",
                    "key": "encryption_status",
                    "value_preview": "Database is NOT encrypted (SQLCipher not used)",
                    "pattern_matched": "unencrypted_database",
                    "severity": "HIGH",
                })
            except sqlite3.DatabaseError:
                findings.append({
                    "source": f"Database/{db_file.name}",
                    "key": "encryption_status",
                    "value_preview": "Database appears to be encrypted",
                    "pattern_matched": "encrypted_database",
                    "severity": "INFO",
                })
                continue

            # Get all tables
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
            tables = [row[0] for row in cursor.fetchall()]

            for table in tables:
                # Get column names
                cursor.execute(f"PRAGMA table_info('{table}');")
                columns = [col[1] for col in cursor.fetchall()]

                # Check column names for sensitive patterns
                sensitive_cols = []
                for col in columns:
                    col_lower = col.lower()
                    if any(s in col_lower for s in ["password", "token", "secret", "key", "auth", "credential", "ssn", "credit"]):
                        sensitive_cols.append(col)

                if sensitive_cols:
                    # Sample data from sensitive columns
                    cols_str = ", ".join(sensitive_cols)
                    cursor.execute(f"SELECT {cols_str} FROM '{table}' LIMIT 3;")
                    samples = cursor.fetchall()

                    findings.append({
                        "source": f"Database/{db_file.name}/{table}",
                        "key": f"sensitive_columns: {', '.join(sensitive_cols)}",
                        "value_preview": str(samples[:2]) if samples else "empty",
                        "pattern_matched": "sensitive_database_columns",
                        "severity": "CRITICAL",
                    })

                # Full text search in all columns
                try:
                    cursor.execute(f"SELECT * FROM '{table}' LIMIT 100;")
                    rows = cursor.fetchall()
                    for row in rows:
                        row_text = " ".join(str(cell) for cell in row if cell)
                        for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                            if pattern.search(row_text):
                                findings.append({
                                    "source": f"Database/{db_file.name}/{table}",
                                    "key": f"row_data_match",
                                    "value_preview": row_text[:100],
                                    "pattern_matched": pattern_name,
                                    "severity": "HIGH",
                                })
                                break
                except sqlite3.OperationalError:
                    pass

            conn.close()

        except sqlite3.Error as e:
            findings.append({
                "source": f"Database/{db_file.name}",
                "key": "error",
                "value_preview": str(e),
                "pattern_matched": "error",
                "severity": "INFO",
            })

    return findings


def scan_plist_files(plist_dir: str) -> list:
    """Scan iOS plist files for sensitive data."""
    findings = []
    plist_path = Path(plist_dir)

    if not plist_path.exists():
        return findings

    for plist_file in plist_path.rglob("*.plist"):
        try:
            with open(plist_file, "r", errors="replace") as f:
                content = f.read()

            for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                matches = pattern.findall(content)
                if matches:
                    findings.append({
                        "source": f"Plist/{plist_file.name}",
                        "key": pattern_name,
                        "value_preview": str(matches[0])[:50],
                        "pattern_matched": pattern_name,
                        "severity": "HIGH",
                    })
        except (OSError, UnicodeDecodeError):
            pass

    return findings


def scan_general_files(data_dir: str) -> list:
    """Scan general files for sensitive data and permission issues."""
    findings = []
    data_path = Path(data_dir)

    for file_path in data_path.rglob("*"):
        if not file_path.is_file():
            continue
        if file_path.suffix in (".so", ".dylib", ".png", ".jpg", ".gif", ".mp3", ".mp4"):
            continue
        if file_path.stat().st_size > 5 * 1024 * 1024:
            continue

        try:
            with open(file_path, "r", errors="replace") as f:
                content = f.read(10000)

            for pattern_name, pattern in SENSITIVE_PATTERNS.items():
                matches = pattern.findall(content)
                if matches:
                    findings.append({
                        "source": f"File/{file_path.relative_to(data_path)}",
                        "key": pattern_name,
                        "value_preview": str(matches[0])[:50],
                        "pattern_matched": pattern_name,
                        "severity": "HIGH" if pattern_name in ("password", "private_key") else "MEDIUM",
                    })
        except (OSError, UnicodeDecodeError):
            pass

    return findings


def main():
    parser = argparse.ArgumentParser(description="Mobile Data Storage Security Scanner")
    parser.add_argument("--data-dir", required=True, help="Extracted app data directory")
    parser.add_argument("--platform", choices=["android", "ios"], default="android")
    parser.add_argument("--output", default="storage_scan.json", help="Output report path")
    args = parser.parse_args()

    data_dir = Path(args.data_dir)
    if not data_dir.exists():
        print(f"[-] Directory not found: {args.data_dir}")
        sys.exit(1)

    all_findings = []

    if args.platform == "android":
        print("[*] Scanning SharedPreferences...")
        all_findings.extend(scan_shared_preferences(str(data_dir / "shared_prefs")))

        print("[*] Scanning SQLite databases...")
        all_findings.extend(scan_sqlite_databases(str(data_dir / "databases")))
    else:
        print("[*] Scanning plist files...")
        all_findings.extend(scan_plist_files(str(data_dir / "Library" / "Preferences")))

        print("[*] Scanning SQLite databases...")
        all_findings.extend(scan_sqlite_databases(str(data_dir)))

    print("[*] Scanning general files...")
    all_findings.extend(scan_general_files(str(data_dir)))

    # Generate report
    severity_counts = {}
    for f in all_findings:
        sev = f["severity"]
        severity_counts[sev] = severity_counts.get(sev, 0) + 1

    report = {
        "scan": {
            "data_directory": str(data_dir),
            "platform": args.platform,
            "date": datetime.now().isoformat(),
        },
        "summary": {
            "total_findings": len(all_findings),
            "by_severity": severity_counts,
        },
        "findings": all_findings,
    }

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)

    print(f"\n[+] Scan complete: {len(all_findings)} findings")
    print(f"[+] Report saved: {args.output}")
    for sev, count in sorted(severity_counts.items()):
        print(f"    {sev}: {count}")


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 1.2 KB
Keep exploring