Certificate Pinning - Zertifikat-Pinning in Apps
Certificate Pinning ist eine Sicherheitstechnik bei der eine Applikation nur bestimmte TLS-Zertifikate oder Public Keys akzeptiert statt dem allgemeinen CA-Vertrauenssystem. Verhindert Man-in-the-Middle-Angriffe selbst wenn Angreifer ein CA-signiertes Zertifikat besitzen. Hauptsächlich in mobilen Apps eingesetzt. Bypass-Methoden: Frida-Hooking, SSL Kill Switch, benutzerdefiniertes Root-Zertifikat. Risiko: Certificate Pinning kann legitimen Traffic-Analyse-Tools blockieren.
Certificate Pinning ist eine Technik um Man-in-the-Middle-Angriffe zu verhindern, die trotz gültigem CA-signiertem Zertifikat ausgeführt werden könnten. Klassische TLS-Validierung vertraut jedem Zertifikat das von einer installierten Root-CA signiert wurde - und es gibt hunderte solcher Root-CAs weltweit. Certificate Pinning reduziert dieses Vertrauen auf ein einzelnes Zertifikat oder einen einzelnen Public Key.
Funktionsprinzip
Warum normales TLS nicht genug ist:
Normales TLS-Validierung:
App → Server: "Hier ist mein Zertifikat (signiert von DigiCert)"
App prüft: Ist DigiCert eine vertrauenswürdige CA? JA → Verbindung OK!
Problem: Angreifer hat kompromittierte CA oder eigene CA:
→ Wenn auf Gerät eine MITM-CA installiert wird (z.B. Burp Suite):
App → Proxy: "Hier ist mein Zertifikat (signiert von PortSwigger CA)"
App prüft: Ist PortSwigger vertrauenswürdig? WENN JA → MITM erfolgreich!
Mit Certificate Pinning:
App hat den echten Server-Public-Key eingespeichert
App → Proxy: "Hier ist mein Zertifikat..."
App prüft: Stimmt Public Key mit gespeichertem Pin überein?
→ Proxy-Zertifikat hat anderen Key → VERBINDUNG ABGELEHNT!
Pinning-Varianten:
1. Certificate Pinning:
→ Vollständiges Zertifikat gespeichert
→ Sehr streng: Auch bei Zertifikats-Erneuerung muss neues Zertifikat gepinnt werden!
→ Risiko: zertifikatsablauf → App bricht → Update nötig
2. Public Key Pinning (empfohlen):
→ Nur Public Key der Leaf- oder Intermediate-CA gespeichert
→ Flexibler: Zertifikat kann erneuert werden solange Key gleich bleibt
→ HPKP (HTTP Public Key Pinning): veraltet, im Browser abgeschafft
→ Mobile Apps: noch weit verbreitet
3. CA-Pinning:
→ Nur die spezifische CA gepinnt (nicht das Zertifikat)
→ Flexibel bei Zertifikatserneuerungen
→ Schwächer: alle Zertifikate dieser CA werden akzeptiert
Hash-Format für Pinning:
# SHA-256 Hash des SubjectPublicKeyInfo:
openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
base64
→ Ergebnis: "Lkcd0...=" → dieser Hash wird in die App eingespeichert
Implementierung
iOS (App Transport Security):
Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSIncludesSubdomains</key><false/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>ABC123...</string>
</dict>
</array>
</dict>
</dict>
</dict>
iOS (URLSession manuell):
extension MyDelegate: URLSessionDelegate {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: ...) {
let serverTrust = challenge.protectionSpace.serverTrust!
let cert = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let pubKey = SecCertificateCopyKey(cert)!
let pubKeyData = SecKeyCopyExternalRepresentation(pubKey, nil)!
let hash = SHA256.hash(data: pubKeyData as Data)
let base64 = Data(hash).base64EncodedString()
if base64 == PINNED_HASH {
completionHandler(.useCredential, ...)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
Android (Network Security Config):
res/xml/network_security_config.xml:
<network-security-config>
<domain-config>
<domain includeSubdomains="false">api.example.com</domain>
<pin-set expiration="2027-01-01">
<pin digest="SHA-256">PRIMARY_PIN_HASH==</pin>
<pin digest="SHA-256">BACKUP_PIN_HASH==</pin>
</pin-set>
</domain-config>
</network-security-config>
AndroidManifest.xml:
<application android:networkSecurityConfig="@xml/network_security_config">
OkHttp (Java/Kotlin):
val client = OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("api.example.com", "sha256/PRIMARY_PIN==")
.add("api.example.com", "sha256/BACKUP_PIN==")
.build()
)
.build()
Pinning Bypass im Pentest
Certificate-Pinning-Bypass-Techniken:
Voraussetzung: Root-Zugriff oder Debugging-Modus auf Gerät
Methode 1 - Frida (Dynamic Instrumentation):
→ Injects JavaScript in laufende App
→ Überschreibt TLS-Validierungsfunktionen zur Laufzeit
frida-ios-dump / objection:
objection -g com.example.app explore
ios sslpinning disable
→ Deaktiviert Pinning in App-Prozess!
Android:
objection -g com.example.app explore
android sslpinning disable
Universal Frida-Script (ssl-kill-switch2-equivalent):
→ Hook auf TrustManager.checkServerTrusted()
→ Gibt immer true zurück → alle Zertifikate akzeptiert!
Methode 2 - SSL Kill Switch (iOS):
→ Tweak für gejailbreakte iOS-Geräte
→ Deaktiviert Pinning systemweit oder pro App
→ Über Cydia/Sileo installierbar
Methode 3 - Binäre Manipulation:
→ APK/IPA dekompilieren (jadx, apktool)
→ Pinning-Validierungscode analysieren
→ Hash-Vergleich entfernen oder durch eigenen Hash ersetzen
→ App neu signieren + installieren
Methode 4 - Debugging mit Xposed Framework (Android):
→ JustTrustMe Module: überschreibt Java SSL-Klassen
→ TrustMeAlready: ähnlich, mehr Kompatibilität
Methode 5 - Emulator mit benutzerdefinierten Root-CA:
→ Android-Emulator: adb root → Zertifikat in System-Store
→ iOS-Simulator: macOS vertraut simulierten Verbindungen
Erkennungsmaßnahmen gegen Bypass:
□ Root/Jailbreak-Detection (SafetyNet/Play Integrity, jailbreak-detect)
□ Debugger-Detection: IsDebuggerPresent, anti-Frida-Checks
□ Frida-Detection: bekannte Frida-Dateipfade/Ports prüfen
□ Integritätsprüfung: App-Signatur beim Start verifizieren
→ Aber: Diese Maßnahmen können ebenfalls umgangen werden!
→ Entschlossenem Angreifer kann Pinning nicht standhalten
→ Ziel: Aufwand erhöhen, nicht unmöglich machen
Best Practices
Certificate Pinning korrekt implementieren:
1. Backup-Pins immer einschließen:
→ Mind. 2 Pins: aktueller Key + Backup-Key
→ Backup-Key: nächster Key der bei Rotation verwendet wird
→ Ohne Backup: Rotation → App funktioniert nicht!
2. Expiration Date setzen:
<pin-set expiration="2027-06-01">
→ Pin-Set läuft ab → App fällt auf normale Validierung zurück
→ Notfall-Fallback wenn Key-Rotation versäumt
3. Reporting aktivieren:
→ Pinning-Fehler an Logging-Endpunkt senden
→ Ermöglicht Erkennung von MITM-Versuchen in Produktion
→ Indikator: viele Pinning-Fehler = MITM-Angriff oder falsche Konfiguration
4. Nicht für alle Verbindungen:
→ Nur für kritische API-Endpunkte (Authentifizierung, Zahlungen)
→ Analytics, CDN, Werbung: kein Pinning (zu viel Rotation)
5. Rotation-Prozess definieren:
□ Neuen Key generieren
□ Neuen Hash in App-Code aufnehmen
□ App-Update deployen (alle User müssen updaten!)
□ Dann: echtes Zertifikat mit neuem Key deployen
□ Altes Pin entfernen (nach Übergangszeit)
Wann auf Pinning verzichten:
□ Wenn kein kontrolliertes Deployment möglich (lange Update-Zyklen)
□ Wenn Backend von CDN/Cloud-Provider betrieben (häufige Rotation)
□ Für öffentliche Web-Apps (Browser-Zugriff: kein Pinning möglich)
→ Alternative: Certificate Transparency Monitoring + HSTS Preloading