mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-23 13:11:02 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
---
|
||||
name: exploiting-insecure-data-storage-in-mobile
|
||||
description: >
|
||||
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.
|
||||
domain: cybersecurity
|
||||
subdomain: mobile-security
|
||||
author: mahipal
|
||||
tags: [mobile-security, android, ios, data-storage, owasp-mobile, penetration-testing]
|
||||
version: 1.0.0
|
||||
license: MIT
|
||||
---
|
||||
# Exploiting Insecure Data Storage in Mobile
|
||||
|
||||
## 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)
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Map Application Data Storage Locations
|
||||
|
||||
**Android storage paths:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
```xml
|
||||
<!-- 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
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Insecure Data Storage Assessment Report
|
||||
|
||||
## Target Application
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Application | [APP_NAME] |
|
||||
| Platform | [Android/iOS] |
|
||||
| Package/Bundle ID | [ID] |
|
||||
| Assessment Date | [DATE] |
|
||||
| Device State | [Rooted/Jailbroken] |
|
||||
|
||||
## Storage Analysis Summary
|
||||
|
||||
| Storage Type | Items Found | Sensitive Data | Encrypted | Risk |
|
||||
|-------------|------------|----------------|-----------|------|
|
||||
| SharedPreferences/Plists | [N] | [YES/NO] | [YES/NO] | [RISK] |
|
||||
| SQLite Databases | [N] | [YES/NO] | [YES/NO] | [RISK] |
|
||||
| Files on Disk | [N] | [YES/NO] | [YES/NO] | [RISK] |
|
||||
| Keychain/Keystore | [N] | [YES/NO] | [YES/NO] | [RISK] |
|
||||
| Backup Data | [N] | [YES/NO] | [YES/NO] | [RISK] |
|
||||
|
||||
## Detailed Findings
|
||||
|
||||
### Finding [N]: [TITLE]
|
||||
|
||||
- **Severity**: [CRITICAL/HIGH/MEDIUM/LOW]
|
||||
- **OWASP Mobile**: M9 - Insecure Data Storage
|
||||
- **CWE**: [CWE-ID]
|
||||
- **Storage Location**: [PATH]
|
||||
- **Data Type**: [credentials/PII/tokens/keys]
|
||||
- **Encrypted**: [YES/NO]
|
||||
- **Evidence**: [SANITIZED_SAMPLE]
|
||||
- **Recommendation**: [REMEDIATION]
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate Actions
|
||||
1. [RECOMMENDATION]
|
||||
|
||||
### Short-Term Improvements
|
||||
1. [RECOMMENDATION]
|
||||
|
||||
### Long-Term Architecture Changes
|
||||
1. [RECOMMENDATION]
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,290 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user