SSL/TLS Certificate Pinning Not Implemented
SSL/TLS Certificate Pinning Not Implemented
Description
The application's HTTPS connections to one or more backend domains were successfully intercepted by a proxy without triggering any certificate rejection, indicating that SSL/TLS certificate pinning is not implemented for those connections.
Without pinning, the application trusts any certificate signed by a CA in the system trust store. An attacker who can position themselves between the device and the server — on a compromised Wi-Fi network, a rogue access point, or a device with a user-installed CA certificate — can silently decrypt all HTTPS traffic, including authentication tokens, session cookies, and sensitive personal data.
Common attack scenarios:
- Corporate proxy / rogue Wi-Fi: An attacker installs a custom CA on the device (or the user is on a network that does so) and decrypts all HTTPS traffic transparently.
- Credential theft: Session tokens and authentication headers are extracted from intercepted requests, enabling account takeover without any visible indication to the user.
- Data tampering: Responses are modified in transit to alter application behavior, inject malicious content, or bypass feature flags and entitlement checks.
- API key extraction: API keys and secrets embedded in request headers or bodies are harvested, enabling unauthorized access to backend services.
Recommendation
Implement certificate or public key pinning for all backend domains under the developer's control. Pin the leaf certificate or, preferably, the public key of the intermediate or root CA to avoid breakage on certificate renewal.
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2026-01-01">
<!-- Primary public key pin (SHA-256 of SubjectPublicKeyInfo) -->
<pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<!-- Backup pin — keep this on a different key/CA to allow rotation -->
<pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
</pin-set>
</domain-config>
</network-security-config>
AndroidManifest.xml:
<application android:networkSecurityConfig="@xml/network_security_config" ...>
val pinSet = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(pinSet)
.build()
<!-- Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</string>
</dict>
</array>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</string>
</dict>
</array>
</dict>
</dict>
</dict>
import TrustKit
let config: [String: Any] = [
kTSKSwizzleNetworkDelegates: true,
kTSKPinnedDomains: [
"api.example.com": [
kTSKIncludeSubdomains: true,
kTSKPublicKeyHashes: [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
]
]
]
]
TrustKit.initSharedInstance(withConfiguration: config)
How to extract the public key pin:
# From a live server
openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
Additional hardening recommendations:
- Always configure at least one backup pin on a different key or CA so you can rotate the primary pin without shipping an emergency update.
- Set an
expirationdate on Android pin-sets and monitor it — an expired pin-set is silently ignored, removing all protection. - Validate pinning enforcement by running the app through a proxy (e.g., mitmproxy) after implementation; a correctly pinned app must fail to connect.
- Pin only domains under your control; exclude third-party SDKs and CDNs where you cannot guarantee certificate stability.
- Never rely solely on client-side pinning — complement it with short-lived tokens, server-side session validation, and anomaly detection on the backend.
Links
- OWASP MASWE - Missing Certificate Pinning (MASWE-0047)
- OWASP MASTG - Testing Custom Certificate Stores and Certificate Pinning (MASTG-TEST-0021)
- OWASP MASVS - MASVS-NETWORK-2
- Android Network Security Configuration: Pin certificates
- Apple App Pinned Domains
- OkHttp Certificate Pinning
Standards
- OWASP_MASVS_v2_1:
- MASVS_NETWORK_2
- OWASP_MASVS_L2:
- MSTG_NETWORK_4
- PCI_STANDARDS:
- REQ_4_2
- SOC2_CONTROLS:
- CC_6_7
- HIPAA_CONTROLS:
- SECURITY252
- OWASP_MOBILE_TOP_10:
- M5_2024