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

  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

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