Mobile apps are increasingly the primary attack surface for data breaches and fraud. According to the IBM Cost of a Data Breach Report 2023, the average cost of a mobile-related breach is $4.45 million — a 15% increase over three years. According to a study by NowSecure, 83% of tested mobile apps had at least one security vulnerability that could expose user data. The OWASP Mobile Application Security Verification Standard (MASVS) identifies critical vulnerability categories across iOS and Android apps, with insecure data storage and improper authentication consistently ranking as the top issues. For QA engineers, mobile security testing requires a specialized toolkit: static analysis (MobSF), dynamic analysis (Frida, Burp Suite), and penetration testing techniques tailored for mobile platforms.

TL;DR: Mobile security testing follows the OWASP MSTG framework: test for insecure data storage (unencrypted SQLite, logs, Keychain misuse), improper authentication, insecure network communication (SSL pinning bypass, MITM), code vulnerabilities (reverse engineering protection), and platform-specific issues (inter-app communication, clipboard attacks).

Mobile Security Testing Fundamentals

Mobile app security testing validates data protection, secure communication, authentication, and resistance to reverse engineering. Following OWASP MASVS (Mobile Application Security Verification Standard) ensures comprehensive coverage across all security domains.

OWASP Mobile Top 10

The OWASP Mobile Security Testing Guide provides the industry-standard methodology for mobile penetration testing.

  1. Improper Platform Usage
  2. Insecure Data Storage
  3. Insecure Communication
  4. Insecure Authentication
  5. Insufficient Cryptography
  6. Insecure Authorization
  7. Client Code Quality
  8. Code Tampering
  9. Reverse Engineering
  10. Extraneous Functionality

“Mobile security testing is the discipline where you think like an attacker, not a tester. The question isn’t whether your app follows the happy path — it’s whether it survives someone actively trying to break it.” — Yuri Kan, Senior QA Lead

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

Official Resources

FAQ

What is the OWASP Mobile Top 10 for mobile security testing?

The OWASP Mobile Top 10 includes: M1 Improper Credential Usage, M2 Inadequate Supply Chain Security, M3 Insecure Authentication/Authorization, M4 Insufficient Input/Output Validation, M5 Insecure Communication, M6 Inadequate Privacy Controls, M7 Insufficient Binary Protections, M8 Security Misconfiguration, M9 Insecure Data Storage, M10 Insufficient Cryptography.

How do I test for insecure data storage in mobile apps?

Check SQLite databases (use DB Browser for SQLite), app logs (adb logcat on Android), SharedPreferences files, and local storage in WebView apps. Use MobSF for automated static analysis. On iOS, check the iOS filesystem using a jailbroken device or Xcode File Browser for sensitive data exposure.

How do I test SSL certificate pinning?

Attempt MITM attacks using Burp Suite with a proxy certificate installed on the device. If SSL pinning is working, requests will fail with certificate validation errors. Use Frida to bypass pinning dynamically for testing: frida-scripts like ‘ssl-kill-switch2’ can disable pinning on jailbroken/rooted devices.

What tools are essential for mobile security testing?

Essential toolkit: MobSF (static and dynamic analysis), Frida (dynamic instrumentation and API hooking), Burp Suite (traffic interception), adb with multiple commands (Android Debug Bridge), Drozer (Android inter-app security), and Objection (runtime mobile exploration without jailbreak).

See Also