Palo Alto Networks PAN-OS contiene un bypass di autenticazione causato da falle nel portale e gateway GlobalProtect, consentendo agli attaccanti di stabilire connessioni VPN non autorizzate; lo sfruttamento richiede l'accesso di rete al portale o gateway.
Questo exploit consente l'accesso VPN non autenticato a un gateway/portale Palo Alto GlobalProtect forgiando un cookie di autenticazione utilizzando esclusivamente il certificato TLS pubblicamente disponibile del server.
GlobalProtect utilizza un cookie pre-autenticazione (portal-userauthcookie) per consentire ai client di autenticarsi. Ecco il difetto fatale:
Flusso Normale:
1. Il client si autentica (nome utente + password)
2. Il server genera un cookie → lo crittografa con la chiave PUBBLICA RSA del server
3. Il client memorizza il cookie crittografato
4. Alla riconnessione, il client invia il cookie → il server lo decifra con la chiave PRIVATA → lo considera attendibile
Il Bug:
Il server CONTROLLA SOLO se il cookie viene decifrato correttamente con la sua chiave privata.
NON verifica CHI lo ha crittografato o se il contenuto in chiaro è legittimo.
Poiché la chiave pubblica RSA è incorporata nel certificato TLS del server (accessibile pubblicamente a chiunque si connetta), qualsiasi attaccante può:
Questo è un difetto classico di autenticazione compromessa — l'uso della crittografia dove era necessaria una firma digitale o un HMAC.
flowchart TD
A["Passo 1: Connessione TCP Raw"] --> B["Passo 2: Inviare ClientHello TLS Personalizzato"]
B --> C["Passo 3: Analizzare ServerHello → Estrarre Certificati DER"]
C --> D["Passo 4: Navigare ASN.1 per Estrarre Chiave Pubblica RSA"]
D --> E["Passo 5: Forgiare Cookie Crittografato PKCS#1 v1.5"]
E --> F["Passo 6: POST verso /ssl-vpn/login.esp"]
F --> G{"Il Server Decifra il Cookie"}
G -->|"Testo in chiaro valido"| H[" Bypass di Autenticazione — Accesso VPN concesso"]
G -->|"Non valido"| I[" Rifiutato"]
Perché raw? Abbiamo bisogno del certificato del server in formato DER (binario raw). Il modulo
ssldi Python completa l'intero handshake TLS internamente e non espone i byte raw del certificato allo stesso modo. Effettuando una connessione TCP raw e inviando un ClientHello costruito a mano, possiamo intercettare la risposta del server a livello di byte.
Un record TLS si presenta così:
┌──────────────────────────────────────────────────┐
│ Intestazione Record TLS (5 byte) │
│ ┌──────┬──────────┬────────────┐ │
│ │ Tipo │ Versione │ Lunghezza │ │
│ │ 0x16 │ 0x03 01 │ 2 byte │ │
│ │(Hshk)│(TLS 1.0) │ │ │
│ └──────┴──────────┴────────────┘ │
│ │
│ Messaggio Handshake │
│ ┌──────┬────────────┬─────────────────────────┐ │
│ │ Tipo │ Lunghezza │ Corpo │ │
│ │ 0x01 │ 3 byte │ (ClientHello) │ │
│ │(CHlo)│ │ │ │
│ └──────┴────────────┴─────────────────────────┘ │
└──────────────────────────────────────────────────┘
Il corpo del ClientHello contiene:
| Campo | Valore | Scopo |
|---|---|---|
| Versione | 0x03 0x03 (TLS 1.2) | Dire al server che parliamo TLS 1.2 |
| Random | 4 byte timestamp + 28 byte casuali | Nonce per l'handshake |
| Session ID | 0x00 (vuoto) | Nessuna ripresa di sessione |
| Cipher Suites | 9 suite incluse TLS_RSA_WITH_AES_128_CBC_SHA | Chiave: includiamo cifrature solo RSA per forzare il server a usare il suo certificato RSA |
| Compressione | 0x00 (nessuna) | Richiesto |
Estensioni incluse:
| Estensione | ID | Scopo |
|---|---|---|
| SNI (Server Name Indication) | 0x0000 | Dire al server a quale hostname ci stiamo connettendo |
| Signature Algorithms | 0x000D | Annunciare quali algoritmi di firma supportiamo |
| Supported Groups | 0x000A | Curve EC supportate (P-256, P-384, P-521) |
| EC Point Formats | 0x000B | Punti EC non compressi |
[!NOTE] Le suite di cifratura includono intenzionalmente cifrature di scambio chiave RSA (
0x002F=TLS_RSA_WITH_AES_128_CBC_SHA). Questo spinge il server a rispondere con il suo certificato RSA piuttosto che uno ECDSA — cosa fondamentale perché l'exploit funziona solo con RSA.
Dopo aver inviato il ClientHello, il server restituisce più record TLS:
Risposta del Server:
┌─────────────────┐
│ ServerHello │ (tipo handshake 2)
├─────────────────┤
│ Certificate │ (tipo handshake 11) ← VOGLIO QUESTO
├─────────────────┤
│ ServerKeyExchange│ (tipo handshake 12, opzionale)
├─────────────────┤
│ ServerHelloDone │ (tipo handshake 14) ← SEGNALE DI STOP
└─────────────────┘
Fase 1 — Rimuovere le intestazioni dei record TLS:
Ogni record TLS ha un'intestazione di 5 byte: [tipo(1)] [versione(2)] [lunghezza(2)]. Il codice scorre tutti i record e, per qualsiasi con tipo == 22 (Handshake), concatena i loro payload:
while i + 5 <= len(data):
t = data[i] # tipo contenuto
rl = (data[i + 3] << 8) | data[i + 4] # lunghezza record
if t == 22: # Handshake
hs.extend(data[i + 5: i + 5 + rl]) # acquisisci payload
i += 5 + rl # prossimo record
Fase 2 — Trovare il messaggio Certificate (tipo 11):
All'interno del flusso dell'handshake, ogni messaggio ha un'intestazione di 4 byte: [tipo(1)] [lunghezza(3)]. Scansioniamo per tipo == 11:
while j + 4 <= len(hs):
ht = hs[j] # tipo handshake
hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3] # lunghezza 3 byte
if ht == 11: # Certificate!
# Analizza l'elenco dei certificati all'interno
Fase 3 — Estrarre singoli certificati DER:
Il messaggio Certificate contiene un elenco di certificati, ciascuno preceduto da una lunghezza di 3 byte:
Corpo del Messaggio Certificate:
┌───────────────────────────────────┐
│ Lunghezza totale certificati (3 byte) │
├───────────────────────────────────┤
│ Lunghezza Cert 1 (3 byte) │
│ Dati DER Cert 1 (variabile) │
├───────────────────────────────────┤
│ Lunghezza Cert 2 (3 byte) │
│ Dati DER Cert 2 (variabile) │
├───────────────────────────────────┤
│ ... │
└───────────────────────────────────┘
Nel nostro test run, abbiamo ottenuto 3 certificati (certificato foglia, CA intermedia, CA radice).
Questa funzione cerca ServerHelloDone (tipo handshake 14), che ci dice che il server ha finito di inviare e possiamo smettere di leggere.
I certificati X.509 sono codificati in DER (Distinguished Encoding Rules), un formato binario basato su ASN.1 (Abstract Syntax Notation One).
Ogni elemento in DER è:
┌─────┬────────┬───────────────────┐
│ Tag │ Lunghezza │ Valore (payload) │
│ 1B │ 1-5B │ variabile │
└─────┴────────┴───────────────────┘