Mobile Security Testing Fundamentals
Mobile app security testing validates data protection, secure communication, authentication, and resistance to reverse engineering. Following OWASP (as discussed in API Security Testing: Complete Guide to OAuth, JWT, and API Keys) MASVS (Mobile Application Security Verification Standard) ensures comprehensive coverage.
OWASP Mobile Top 10
- Improper Platform Usage
- Insecure Data Storage
- Insecure Communication
- Insecure Authentication
- Insufficient Cryptography
- Insecure Authorization
- Client Code Quality
- Code Tampering
- Reverse Engineering
- Extraneous Functionality
iOS Security Testing
Insecure Data Storage (iOS)
// INSECURE: UserDefaults (plaintext storage)
UserDefaults.standard.set("sensitive_token", forKey: "auth_token")
// SECURE: Keychain
import Security
func (as discussed in [OWASP ZAP Automation: Security Scanning in CI/CD](/blog/owasp-zap-automation)) saveToKeychain(key: String, value: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: value.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}
func getFromKeychain(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess, let data = result as? Data {
return String(data: data, encoding: .utf8)
}
return nil
}
Certificate Pinning (iOS)
// Implement certificate pinning
class SecurityManager: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Pinned certificate (SHA-256 hash)
let pinnedCertificateHash = "sha256_hash_of_certificate"
let serverCertificateData = SecCertificateCopyData(certificate) as Data
let serverCertificateHash = serverCertificateData.sha256()
if serverCertificateHash == pinnedCertificateHash {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
Testing iOS Apps with Objection
# Install Objection
pip3 install objection
# Connect to iOS device
iproxy 2222 22
# Attach to app
objection --gadget "com.example.app" explore
# Enumerate data storage
ios keychain dump
ios nsuserdefaults get
ios plist cat /path/to/plist
# Bypass jailbreak detection
ios jailbreak disable
# Bypass SSL pinning
ios sslpinning disable
# Monitor crypto operations
ios monitor crypto
Android Security Testing
Insecure Data Storage (Android)
// INSECURE: SharedPreferences (plaintext)
val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
prefs.edit().putString("auth_token", "sensitive_token").apply()
// SECURE: EncryptedSharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptedPrefs.edit().putString("auth_token", "sensitive_token").apply()
Certificate Pinning (Android)
// Network security config (res/xml/network_security_config.xml)
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
(as discussed in [Penetration Testing Basics for QA Testers](/blog/penetration-testing-basics)) <domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set>
<pin digest="SHA-256">base64_encoded_public_key_hash=</pin>
<!-- Backup pin -->
<pin digest="SHA-256">backup_key_hash=</pin>
</pin-set>
</domain-config>
</network-security-config>
// Programmatic certificate pinning with OkHttp
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/primary_key_hash")
.add("api.example.com", "sha256/backup_key_hash")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
Testing Android Apps with Frida
# Install Frida
pip install frida-tools
# Start Frida server on device
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
# List running apps
frida-ps -U
# Attach to app
frida -U -f com.example.app
# Bypass root detection
frida -U -l bypass-root.js -f com.example.app
// bypass-root.js - Frida script
Java.perform(function() {
// Hook root detection
var RootDetection = Java.use('com.example.RootDetection');
RootDetection.isRooted.implementation = function() {
console.log('[+] Root detection bypassed');
return false;
};
// Bypass SSL pinning
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
console.log('[+] SSL pinning bypassed');
return;
};
});
Static Analysis
iOS - MobSF
# Run Mobile Security Framework
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf
# Upload IPA file at http://localhost:8000
# MobSF performs:
# - Binary analysis
# - Plist analysis
# - Hardcoded secrets detection
# - Insecure API usage
# - Code quality issues
Android - APKTool
# Decompile APK
apktool d app.apk -o decompiled
# Analyze manifest
cat decompiled/AndroidManifest.xml
# Search for hardcoded secrets
grep -r "api_key" decompiled/
grep -r "password" decompiled/
# Check for debuggable flag
grep -i "debuggable" decompiled/AndroidManifest.xml
# Rebuild APK (for testing)
apktool b decompiled -o modified.apk
# Sign APK
jarsigner -keystore test.keystore modified.apk alias_name
Dynamic Analysis
Network Traffic Analysis
# Setup proxy (Charles/Burp Suite)
# iOS: Install proxy certificate
Settings → General → Profile → Install Charles Certificate
# Android: Install certificate as user/system CA
adb push charles-ssl-proxying-certificate.pem /sdcard/
Settings → Security → Install from SD card
# Intercept traffic
# 1. Configure device proxy to computer IP:8888
# 2. Bypass certificate pinning (Objection/Frida)
# 3. Monitor API requests in proxy
Runtime Manipulation
# Frida script - Modify method return values
import frida
import sys
device = frida.get_usb_device()
pid = device.spawn(["com.example.app"])
session = device.attach(pid)
script = session.create_script("""
Java.perform(function() {
var MainActivity = Java.use('com.example.MainActivity');
MainActivity.isPremiumUser.implementation = function() {
console.log('[+] isPremiumUser() called');
return true; // Always return premium
};
});
""")
script.load()
device.resume(pid)
sys.stdin.read()
Automated Security Testing
Continuous Security Testing
# .github/workflows/mobile-security.yml
name: Mobile Security Scan
on: [push]
jobs:
android-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build APK
run: ./gradlew assembleRelease
- name: MobSF Scan
run: |
docker run --rm -v $(pwd):/app opensecurity/mobile-security-framework-mobsf \
mobsf --apk /app/app-release.apk --output /app/mobsf-report.json
- name: Check vulnerabilities
run: |
HIGH_VULNS=$(jq '.high_severity_count' mobsf-report.json)
if [ "$HIGH_VULNS" -gt 0 ]; then
echo "Found $HIGH_VULNS high severity issues"
exit 1
fi
Best Practices
Secure Coding Checklist
## Data Storage
- [ ] No sensitive data in UserDefaults/SharedPreferences
- [ ] Use Keychain (iOS) / EncryptedSharedPreferences (Android)
- [ ] Enable file encryption
- [ ] Clear sensitive data from memory
## Network Security
- [ ] Certificate pinning implemented
- [ ] Use TLS 1.2+
- [ ] Validate SSL certificates
- [ ] No cleartext traffic
## Authentication
- [ ] Biometric authentication for sensitive actions
- [ ] Secure token storage
- [ ] Proper session management
- [ ] Auto-logout on inactivity
## Code Protection
- [ ] Obfuscation enabled (ProGuard/R8)
- [ ] Root/Jailbreak detection
- [ ] Tamper detection
- [ ] No hardcoded secrets
Conclusion
Mobile app security testing requires platform-specific knowledge and specialized tools. By combining static analysis, dynamic testing, and runtime manipulation, QA engineers can identify vulnerabilities before attackers exploit them.
Key Takeaways:
- Follow OWASP MASVS guidelines
- Secure data storage with platform encryption APIs
- Implement certificate pinning
- Use MobSF for automated static analysis
- Test with Frida/Objection for runtime analysis
- Integrate security testing in CI/CD
- Never store secrets in code or shared preferences