Mobile Network Security
What Is Broken and Why
Mobile apps fail network security when they allow cleartext HTTP traffic, disable TLS certificate validation, or implement certificate pinning incorrectly. Custom X509TrustManager implementations that accept all certificates (empty checkServerTrusted) are a common developer shortcut that makes the entire TLS layer useless. ATS exceptions in iOS Info.plist or Android Network Security Configuration that allow arbitrary cleartext expose all traffic to MITM. Apps that call onReceivedSslError().proceed() in WebViewClient bypass all certificate errors. Certificate pinning without key backup pins causes production outages, so developers remove pinning — leaving no protection.
Key Signals
- Android:
android:networkSecurityConfig pointing to XML with <domain-config cleartextTrafficPermitted="true">
- Android:
android:usesCleartextTraffic="true" in manifest
- iOS:
NSAllowsArbitraryLoads: true in Info.plist ATS section
- Custom
X509TrustManager with empty checkServerTrusted() method body
HostnameVerifier returning true for all hosts: ALLOW_ALL_HOSTNAME_VERIFIER
SSLContext.init(null, arrayOf(trustAllManager), null)
- WebViewClient
onReceivedSslError calling handler.proceed()
- TLS 1.0/1.1 explicitly enabled via
SSLParameters.setProtocols()
- No
pin-set in Network Security Configuration for sensitive domains
- iOS
NSURLSessionDelegate returning no error for invalid certificates
URLSession.shared with no custom delegate (no pinning) for high-value endpoints
Methodology
Setup MITM proxy:
- Install Burp/mitmproxy CA cert on device (Android: Settings > Security; iOS: Settings > General > VPN & Device Management)
- Configure device proxy to point at Burp listener
- Launch app — observe if traffic appears in proxy (cleartext) or throws certificate errors (pinning)
Android static analysis:
apktool d app.apk — check AndroidManifest.xml for usesCleartextTraffic, networkSecurityConfig
- Review
res/xml/network_security_config.xml for cleartext rules and pin-set presence
- Search decompiled source for
TrustManager, HostnameVerifier, ALLOW_ALL, onReceivedSslError
- Search for
SSLContext.init, HttpsURLConnection.setDefaultHostnameVerifier
- Check OkHttp client config:
OkHttpClient.Builder() for custom sslSocketFactory
iOS static analysis:
- Extract IPA — inspect
Info.plist for NSAppTransportSecurity exceptions
- Search source for
URLSession, NSURLConnection, custom URLSessionDelegate methods
- Check
didReceiveChallenge delegate for completionHandler(.useCredential, ...)
- Look for TrustKit, Alamofire, or custom pinning implementation
Dynamic analysis:
- With Burp proxy active — if app connects normally: no pinning or pinning bypass available
- Attempt SSL kill switch: objection
ios sslpinning disable or Android android sslpinning disable
- Use Frida script to hook
TrustManagerImpl.checkServerTrusted or SecTrustEvaluate
Payloads & Tools
# objection — disable SSL pinning (Android/iOS)
objection --gadget TARGET run android sslpinning disable
objection --gadget TARGET run ios sslpinning disable
# Frida — Android: bypass TrustManager
Java.perform(function() {
var TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var TM = Java.registerClass({
name: "FakeTrustManager", implements: [TrustManager],
methods: { checkClientTrusted: function(){}, checkServerTrusted: function(){},
getAcceptedIssuers: function(){ return []; } }
});
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;","[Ljavax.net.ssl.TrustManager;","java.security.SecureRandom")
.implementation = function(km, tm, sr) { this.init(km, [TM.$new()], sr); };
});
# iOS — SSL kill switch (jailbroken device)
# Install SSL Kill Switch 3 via Cydia/Sileo
# OR use Frida script ssl-kill-switch2.js
# Check ATS config in IPA
unzip app.ipa; grep -A20 "NSAppTransportSecurity" Payload/App.app/Info.plist
# Check Android Network Security Config
apktool d app.apk && cat app/res/xml/network_security_config.xml
Bypass Techniques
- objection sslpinning disable — hooks common pinning libraries (OkHttp, TrustKit, Alamofire) at runtime
- SSL Kill Switch 3 — jailbroken iOS; patches
SecTrustEvaluate at OS level
- Frida TrustManager replacement — replaces the app's trust manager with one that accepts all certs
- MagiskTrustUserCerts — on rooted Android, installs CA cert as system cert (bypasses Android 14+ restrictions)
- apk-mitm — patches APK to disable pinning statically without needing runtime instrumentation
- Network Security Config override — repack APK with
cleartextTrafficPermitted="true" and custom trust anchors
Exploitation Scenarios
Scenario 1 — Empty TrustManager MITM
Setup: App uses SSLContext.init(null, arrayOf(TrustAllManager()), null) to avoid pinning errors in dev, shipped to production. → Trigger: Attacker on same Wi-Fi runs mitmproxy. → Impact: All HTTPS traffic decrypted — credentials, session tokens, PII visible.
Scenario 2 — ATS Exception Cleartext
Setup: iOS app sets NSAllowsArbitraryLoads: true for legacy API compatibility. → Trigger: Network interception on hotel Wi-Fi. → Impact: Plaintext auth tokens and API responses captured.
Scenario 3 — WebView onReceivedSslError Bypass
Setup: WebViewClient overrides onReceivedSslError and calls handler.proceed(). → Trigger: MITM proxy presents a self-signed cert to the WebView. → Impact: Victim navigates authenticated WebView session through attacker's proxy.
False Positives
cleartextTrafficPermitted="true" only for non-sensitive domains (analytics, CDN assets) with sensitive traffic separately pinned
- Custom
URLSessionDelegate that validates the cert chain manually and only accepts the prod CA
- Debug-only bypass code that is stripped in release builds (
BuildConfig.DEBUG guard)
- Certificate pinning disabled for localhost (test environment) — confirm production build behavior
Fix Patterns
<!-- Android Network Security Config — correct -->
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.TARGET</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">SPKI_HASH_HERE</pin>
<pin digest="SHA-256">BACKUP_SPKI_HASH</pin> <!-- Always include backup pin -->
</pin-set>
</domain-config>
</network-security-config>
// iOS — URLSession pinning via TrustKit or manual
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
validateCert(serverTrust) else { // compare SPKI hash
completionHandler(.cancelAuthenticationChallenge, nil); return
}
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
Related Skills
[[cors-misconfig]] on mobile backend APIs mirrors the same trust boundary issue as missing certificate pinning — both allow a network-positioned attacker to intercept or manipulate authenticated traffic. An empty TrustManager is functionally equivalent to [[ssrf]] from the attacker's perspective: the server (or in this case the app) makes authenticated requests to an unverified destination. [[mobile-insecure-storage]] is the fallback attack when network interception fails — if TLS is properly pinned, credentials may still be extractable from local storage.
1---2name: mobile-network-security3description: Detects insecure network communication in mobile apps (Android/iOS). Trigger on: cleartext HTTP, TLS misconfiguration, certificate pinning bypass, hostname verification disabled, allowCleartextTraffic, NSAllowsArbitraryLoads, ATS exceptions, custom TrustManager, ALLOW_ALL_HOSTNAME_VERIFIER, TLS 1.0/1.1, weak cipher suites, certificate pinning absent, Network Security Configuration, onReceivedSslError, SSLSocket, OkHttp, NSURL, URLSession, certificate transparency, HSTS, MITM. Covers MASVS-NETWORK-1 (TLS required) and MASVS-NETWORK-2 (certificate validation).4license: MIT5---67# Mobile Network Security89## What Is Broken and Why1011Mobile apps fail network security when they allow cleartext HTTP traffic, disable TLS certificate validation, or implement certificate pinning incorrectly. Custom `X509TrustManager` implementations that accept all certificates (empty `checkServerTrusted`) are a common developer shortcut that makes the entire TLS layer useless. ATS exceptions in iOS Info.plist or Android Network Security Configuration that allow arbitrary cleartext expose all traffic to MITM. Apps that call `onReceivedSslError().proceed()` in WebViewClient bypass all certificate errors. Certificate pinning without key backup pins causes production outages, so developers remove pinning — leaving no protection.1213## Key Signals1415- Android: `android:networkSecurityConfig` pointing to XML with `<domain-config cleartextTrafficPermitted="true">`16- Android: `android:usesCleartextTraffic="true"` in manifest17- iOS: `NSAllowsArbitraryLoads: true` in Info.plist ATS section18- Custom `X509TrustManager` with empty `checkServerTrusted()` method body19- `HostnameVerifier` returning `true` for all hosts: `ALLOW_ALL_HOSTNAME_VERIFIER`20- `SSLContext.init(null, arrayOf(trustAllManager), null)`21- WebViewClient `onReceivedSslError` calling `handler.proceed()`22- TLS 1.0/1.1 explicitly enabled via `SSLParameters.setProtocols()`23- No `pin-set` in Network Security Configuration for sensitive domains24- iOS `NSURLSessionDelegate` returning no error for invalid certificates25- `URLSession.shared` with no custom delegate (no pinning) for high-value endpoints2627## Methodology2829**Setup MITM proxy:**301. Install Burp/mitmproxy CA cert on device (Android: Settings > Security; iOS: Settings > General > VPN & Device Management)312. Configure device proxy to point at Burp listener323. Launch app — observe if traffic appears in proxy (cleartext) or throws certificate errors (pinning)3334**Android static analysis:**351. `apktool d app.apk` — check `AndroidManifest.xml` for `usesCleartextTraffic`, `networkSecurityConfig`362. Review `res/xml/network_security_config.xml` for cleartext rules and pin-set presence373. Search decompiled source for `TrustManager`, `HostnameVerifier`, `ALLOW_ALL`, `onReceivedSslError`384. Search for `SSLContext.init`, `HttpsURLConnection.setDefaultHostnameVerifier`395. Check OkHttp client config: `OkHttpClient.Builder()` for custom `sslSocketFactory`4041**iOS static analysis:**421. Extract IPA — inspect `Info.plist` for `NSAppTransportSecurity` exceptions432. Search source for `URLSession`, `NSURLConnection`, custom `URLSessionDelegate` methods443. Check `didReceiveChallenge` delegate for `completionHandler(.useCredential, ...)`454. Look for TrustKit, Alamofire, or custom pinning implementation4647**Dynamic analysis:**481. With Burp proxy active — if app connects normally: no pinning or pinning bypass available492. Attempt SSL kill switch: objection `ios sslpinning disable` or Android `android sslpinning disable`503. Use Frida script to hook `TrustManagerImpl.checkServerTrusted` or `SecTrustEvaluate`5152## Payloads & Tools5354```bash55# objection — disable SSL pinning (Android/iOS)56objection --gadget TARGET run android sslpinning disable57objection --gadget TARGET run ios sslpinning disable5859# Frida — Android: bypass TrustManager60Java.perform(function() {61 var TrustManager = Java.use("javax.net.ssl.X509TrustManager");62 var SSLContext = Java.use("javax.net.ssl.SSLContext");63 var TM = Java.registerClass({64 name: "FakeTrustManager", implements: [TrustManager],65 methods: { checkClientTrusted: function(){}, checkServerTrusted: function(){},66 getAcceptedIssuers: function(){ return []; } }67 });68 SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;","[Ljavax.net.ssl.TrustManager;","java.security.SecureRandom")69 .implementation = function(km, tm, sr) { this.init(km, [TM.$new()], sr); };70});7172# iOS — SSL kill switch (jailbroken device)73# Install SSL Kill Switch 3 via Cydia/Sileo74# OR use Frida script ssl-kill-switch2.js7576# Check ATS config in IPA77unzip app.ipa; grep -A20 "NSAppTransportSecurity" Payload/App.app/Info.plist7879# Check Android Network Security Config80apktool d app.apk && cat app/res/xml/network_security_config.xml81```8283## Bypass Techniques8485- **objection sslpinning disable** — hooks common pinning libraries (OkHttp, TrustKit, Alamofire) at runtime86- **SSL Kill Switch 3** — jailbroken iOS; patches `SecTrustEvaluate` at OS level87- **Frida TrustManager replacement** — replaces the app's trust manager with one that accepts all certs88- **MagiskTrustUserCerts** — on rooted Android, installs CA cert as system cert (bypasses Android 14+ restrictions)89- **apk-mitm** — patches APK to disable pinning statically without needing runtime instrumentation90- **Network Security Config override** — repack APK with `cleartextTrafficPermitted="true"` and custom trust anchors9192## Exploitation Scenarios9394**Scenario 1 — Empty TrustManager MITM**95Setup: App uses `SSLContext.init(null, arrayOf(TrustAllManager()), null)` to avoid pinning errors in dev, shipped to production. → Trigger: Attacker on same Wi-Fi runs mitmproxy. → Impact: All HTTPS traffic decrypted — credentials, session tokens, PII visible.9697**Scenario 2 — ATS Exception Cleartext**98Setup: iOS app sets `NSAllowsArbitraryLoads: true` for legacy API compatibility. → Trigger: Network interception on hotel Wi-Fi. → Impact: Plaintext auth tokens and API responses captured.99100**Scenario 3 — WebView onReceivedSslError Bypass**101Setup: WebViewClient overrides `onReceivedSslError` and calls `handler.proceed()`. → Trigger: MITM proxy presents a self-signed cert to the WebView. → Impact: Victim navigates authenticated WebView session through attacker's proxy.102103## False Positives104105- `cleartextTrafficPermitted="true"` only for non-sensitive domains (analytics, CDN assets) with sensitive traffic separately pinned106- Custom `URLSessionDelegate` that validates the cert chain manually and only accepts the prod CA107- Debug-only bypass code that is stripped in release builds (`BuildConfig.DEBUG` guard)108- Certificate pinning disabled for localhost (test environment) — confirm production build behavior109110## Fix Patterns111112```xml113<!-- Android Network Security Config — correct -->114<network-security-config>115 <domain-config>116 <domain includeSubdomains="true">api.TARGET</domain>117 <pin-set expiration="2026-01-01">118 <pin digest="SHA-256">SPKI_HASH_HERE</pin>119 <pin digest="SHA-256">BACKUP_SPKI_HASH</pin> <!-- Always include backup pin -->120 </pin-set>121 </domain-config>122</network-security-config>123```124125```swift126// iOS — URLSession pinning via TrustKit or manual127func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,128 completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {129 guard let serverTrust = challenge.protectionSpace.serverTrust,130 validateCert(serverTrust) else { // compare SPKI hash131 completionHandler(.cancelAuthenticationChallenge, nil); return132 }133 completionHandler(.useCredential, URLCredential(trust: serverTrust))134}135```136137## Related Skills138139[[cors-misconfig]] on mobile backend APIs mirrors the same trust boundary issue as missing certificate pinning — both allow a network-positioned attacker to intercept or manipulate authenticated traffic. An empty `TrustManager` is functionally equivalent to [[ssrf]] from the attacker's perspective: the server (or in this case the app) makes authenticated requests to an unverified destination. [[mobile-insecure-storage]] is the fallback attack when network interception fails — if TLS is properly pinned, credentials may still be extractable from local storage.