Protecting mobile applications against traffic interception (MITM)
On corporate Wi-Fi or when connecting to attacker's access point Burp Suite intercepts all app HTTPS traffic in 2 minutes — just set proxy and add certificate to trusted. Most apps trust any certificate signed by system CA. This is MITM.
TLS as basic level
HTTPS without Certificate Pinning protects only from passive eavesdropping, not from certificate substitution attack. Attacker with ability to add their CA to trusted (via MDM, social engineering, on corporate device) reads all traffic.
Minimal hygiene: TLS 1.2 minimum, TLS 1.3 as goal. Disable outdated cipher suites (RC4, 3DES, NULL). On Android via network_security_config.xml:
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
cleartextTrafficPermitted="false" blocks HTTP at OS level. Mandatory for any app working with user data.
Certificate Pinning
Pinning — anchoring specific certificate or server public key in app. Even if attacker substituted their CA, connection breaks: server certificate doesn't match pinned.
Public key pinning vs certificate pinning. Certificate pinning — simpler, but on certificate rotation you must update app. Public key pinning — binding to SubjectPublicKeyInfo hash: can use key for new certificate with same key. Recommend key pinning.
On Android via OkHttp:
val client = OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // backup pin
.build()
)
.build()
Two pins mandatory — primary and backup. If only one, on key rotation app stops working for all users until update.
On iOS via URLSession with custom 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
}
let publicKey = SecCertificateCopyKey(certificate)
// compare with pinned key
}
TrustKit (iOS) and Conscrypt (Android) — libraries simplifying implementation. TrustKit supports Info.plist configuration and violation reporting.
Bypass and countermeasures
Standard Frida script ssl-unpinning.js hooks TrustManagerImpl.checkServerTrusted(), SSLContext.init(), OkHttp CertificatePinner.check() and bypasses most popular implementations in seconds. Doesn't mean pinning is useless — it raises entry threshold from "downloaded Burp" to "installed Frida on rooted device and found script".
Strengthening: implement pinning in native code (JNI), don't use standard API that automatic scripts hook, add debugger detection before network requests.
Network Security Config on Android 7+. trust-anchors can be limited to only system CAs (removing user-installed). App won't trust certificate installed by user via settings — Burp proxy immediately stops working without root.
Certificate Transparency
CT logs — public registry of all issued certificates. Browsers require CT SCT (Signed Certificate Timestamp) for trust. On mobile — optional additional check: verify server certificate present in CT logs. Protects against issuance of shadow certificates for domain.
HPKP and its problems
HTTP Public Key Pinning (HPKP) — server header with pinned keys. Browsers supported it, then removed due to risks (can permanently lock site with wrong config). In mobile apps — don't use, do pinning on client.
Timeline for implementing pinning with two backup keys, native implementation on Android and iOS, plus network_security_config configuration — 2–3 days with certificate rotation scenario testing.







