Palo Alto Networks PAN-OS enthält eine Authentifizierungsumgehung, die durch Schwachstellen im GlobalProtect-Portal und -Gateway verursacht wird und Angreifern ermöglicht, unbefugte VPN-Verbindungen herzustellen. Der Exploit erfordert Netzwerkzugriff auf das Portal oder Gateway.
Dieser Exploit ermöglicht unauthentifizierten VPN-Zugriff auf ein Palo Alto GlobalProtect Gateway/Portal, indem ein Authentifizierungs-Cookie allein unter Verwendung des öffentlich verfügbaren TLS-Zertifikats des Servers gefälscht wird.
GlobalProtect verwendet ein Pre-Authentifizierungs-Cookie (portal-userauthcookie), damit Clients sich authentifizieren können. Hier liegt der fatale Fehler:
Normaler Ablauf:
1. Client authentifiziert sich (Benutzername + Passwort)
2. Server generiert ein Cookie → verschlüsselt es mit dem RSA-ÖFFENTLICHEN Schlüssel des Servers
3. Client speichert das verschlüsselte Cookie
4. Bei erneuter Verbindung sendet der Client das Cookie → Server entschlüsselt mit PRIVATEM Schlüssel → vertraut ihm
Der Fehler:
Der Server prüft NUR, ob das Cookie erfolgreich mit seinem privaten Schlüssel entschlüsselt werden kann.
Er überprüft NICHT, WER es verschlüsselt hat oder ob der Klartextinhalt legitim ist.
Da der RSA-öffentliche Schlüssel im TLS-Zertifikat des Servers eingebettet ist (öffentlich zugänglich für jeden, der eine Verbindung herstellt), kann jeder Angreifer:
Dies ist ein Lehrbuchfehler der Authentifizierung – die Verwendung von Verschlüsselung, wo eine digitale Signatur oder HMAC nötig gewesen wäre.
flowchart TD
A["Schritt 1: Raw TCP Connect"] --> B["Schritt 2: Maßgeschneidertes TLS ClientHello senden"]
B --> C["Schritt 3: ServerHello parsen → DER-Zertifikate extrahieren"]
C --> D["Schritt 4: ASN.1 durchlaufen, um RSA-öffentlichen Schlüssel zu extrahieren"]
D --> E["Schritt 5: PKCS#1 v1.5 verschlüsseltes Cookie fälschen"]
E --> F["Schritt 6: POST an /ssl-vpn/login.esp"]
F --> G{"Server entschlüsselt Cookie"}
G -->|"Gültiger Klartext"| H[" Auth Bypass — VPN-Zugriff gewährt"]
G -->|"Ungültig"| I[" Abgelehnt"]
Warum raw? Wir benötigen das Serverzertifikat im DER-Format (rohes Binärformat). Das
ssl-Modul von Python führt den vollständigen TLS-Handshake intern durch und gibt die rohen Zertifikatsbytes nicht auf die gleiche Weise preis. Indem wir eine rohe TCP-Verbindung herstellen und ein handgefertigtes ClientHello senden, können wir die Antwort des Servers auf Bitebene abfangen.
Ein TLS-Datensatz sieht so aus:
┌──────────────────────────────────────────────────┐
│ TLS Record Header (5 Bytes) │
│ ┌──────┬──────────┬────────────┐ │
│ │ Type │ Version │ Länge │ │
│ │ 0x16 │ 0x03 01 │ 2 Bytes │ │
│ │(Hshk)│(TLS 1.0) │ │ │
│ └──────┴──────────┴────────────┘ │
│ │
│ Handshake-Nachricht │
│ ┌──────┬────────────┬─────────────────────────┐ │
│ │ Type │ Länge │ Body │ │
│ │ 0x01 │ 3 Bytes │ (ClientHello) │ │
│ │(CHlo)│ │ │ │
│ └──────┴────────────┴─────────────────────────┘ │
└──────────────────────────────────────────────────┘
Der ClientHello-Body enthält:
| Feld | Wert | Zweck |
|---|---|---|
| Version | 0x03 0x03 (TLS 1.2) | Server mitteilen, dass wir TLS 1.2 sprechen |
| Random | 4-Byte-Zeitstempel + 28 zufällige Bytes | Nonce für den Handshake |
| Session ID | 0x00 (leer) | Keine Sitzungswiederaufnahme |
| Cipher Suites | 9 Suiten inklusive TLS_RSA_WITH_AES_128_CBC_SHA | Schlüssel: Wir nehmen nur RSA-Cipher auf, um den Server zu zwingen, sein RSA-Zertifikat zu verwenden |
| Compression | 0x00 (keine) | Erforderlich |
Enthaltene Erweiterungen:
| Erweiterung | ID | Zweck |
|---|---|---|
| SNI (Server Name Indication) | 0x0000 | Dem Server mitteilen, mit welchem Hostnamen wir verbinden |
| Signature Algorithms | 0x000D | Unsere unterstützten Signaturalgorithmen bekannt geben |
| Supported Groups | 0x000A | Unterstützte EC-Kurven (P-256, P-384, P-521) |
| EC Point Formats | 0x000B | Unkomprimierte EC-Punkte |
[!HINWEIS] Die Cipher-Suiten enthalten absichtlich RSA-Schlüsselaustausch-Cipher (
0x002F=TLS_RSA_WITH_AES_128_CBC_SHA). Dies drängt den Server, mit seinem RSA-Zertifikat statt einem ECDSA-Zertifikat zu antworten – was entscheidend ist, da der Exploit nur mit RSA funktioniert.
Nach dem Senden des ClientHello sendet der Server mehrere TLS-Datensätze zurück:
Server-Antwort:
┌─────────────────┐
│ ServerHello │ (Handshake-Typ 2)
├─────────────────┤
│ Certificate │ (Handshake-Typ 11) ← DAS WOLLEN WIR
├─────────────────┤
│ ServerKeyExchange│ (Handshake-Typ 12, optional)
├─────────────────┤
│ ServerHelloDone │ (Handshake-Typ 14) ← STOPP-SIGNAL
└─────────────────┘
Phase 1 — TLS-Datensatz-Header entfernen:
Jeder TLS-Datensatz hat einen 5-Byte-Header: [type(1)] [version(2)] [length(2)]. Der Code durchsucht alle Datensätze und verkettert für alle mit type == 22 (Handshake) ihre Nutzdaten:
while i + 5 <= len(data):
t = data[i] # content type
rl = (data[i + 3] << 8) | data[i + 4] # record length
if t == 22: # Handshake
hs.extend(data[i + 5: i + 5 + rl]) # Nutzdaten holen
i += 5 + rl # nächster Datensatz
Phase 2 — Die Certificate-Nachricht (Typ 11) finden:
Innerhalb des Handshake-Streams hat jede Nachricht einen 4-Byte-Header: [type(1)] [length(3)]. Wir suchen nach type == 11:
while j + 4 <= len(hs):
ht = hs[j] # handshake type
hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3] # 3-Byte-Länge
if ht == 11: # Certificate!
# Zertifikatsliste innen parsen
Phase 3 — Einzelne DER-Zertifikate extrahieren:
Die Certificate-Nachricht enthält eine Liste von Zertifikaten, jedes mit einem 3-Byte-Längen-Präfix:
Certificate Message Body:
┌───────────────────────────────────┐
│ Gesamt-Zertifikatslänge (3 Bytes) │
├───────────────────────────────────┤
│ Zert 1 Länge (3 Bytes) │
│ Zert 1 DER-Daten (variabel) │
├───────────────────────────────────┤
│ Zert 2 Länge (3 Bytes) │
│ Zert 2 DER-Daten (variabel) │
├───────────────────────────────────┤
│ ... │
└───────────────────────────────────┘
In unserem Testlauf haben wir 3 Zertifikate erhalten (Blattzertifikat, Zwischen-CA, Root-CA).
Diese Funktion sucht nach ServerHelloDone (Handshake-Typ 14), was uns sagt, dass der Server fertig gesendet hat und wir mit dem Lesen aufhören können.
X.509-Zertifikate sind in DER (Distinguished Encoding Rules) codiert, einem binären Format basierend auf ASN.1 (Abstract Syntax Notation One).
Jedes Element in DER ist: