Palo Alto Networks PAN-OS contient un contournement d'authentification causé par des failles dans le portail et la passerelle GlobalProtect, permettant aux attaquants d'établir des connexions VPN non autorisées. L'exploitation nécessite un accès réseau au portail ou à la passerelle.
Cette exploitation permet un accès VPN non authentifié à une passerelle/portail Palo Alto GlobalProtect en forgeant un cookie d'authentification en utilisant uniquement le certificat TLS public du serveur.
GlobalProtect utilise un cookie de pré-authentification (portal-userauthcookie) pour permettre aux clients de s'authentifier. Voici le défaut fatal :
Flux normal :
1. Le client s'authentifie (nom d'utilisateur + mot de passe)
2. Le serveur génère un cookie → le chiffre avec la clé PUBLIQUE RSA du serveur
3. Le client stocke le cookie chiffré
4. Lors de la reconnexion, le client envoie le cookie → le serveur le déchiffre avec la clé PRIVÉE → lui fait confiance
Le bogue :
Le serveur vérifie UNIQUEMENT si le cookie se déchiffre correctement avec sa clé privée.
Il ne vérifie PAS QUI l'a chiffré ni si le contenu en clair est légitime.
Puisque la clé publique RSA est intégrée au certificat TLS du serveur (accessible publiquement à quiconque se connecte), tout attaquant peut :
C'est un défaut d'authentification brisée typique — utiliser le chiffrement là où une signature numérique ou un HMAC était nécessaire.
flowchart TD
A["Étape 1 : Connexion TCP brute"] --> B["Étape 2 : Envoi d'un ClientHello TLS façonné"]
B --> C["Étape 3 : Analyse du ServerHello → Extraction des certificats DER"]
C --> D["Étape 4 : Parcours de l'ASN.1 pour extraire la clé publique RSA"]
D --> E["Étape 5 : Forge d'un cookie chiffré PKCS#1 v1.5"]
E --> F["Étape 6 : POST vers /ssl-vpn/login.esp"]
F --> G{"Le serveur déchiffre le cookie"}
G -->|"Texte en clair valide"| H[" Contournement d'auth — Accès VPN accordé"]
G -->|"Invalide"| I[" Rejeté"]
Pourquoi brut ? Nous avons besoin du certificat du serveur au format DER (binaire brut). Le module
sslde Python effectue la poignée de main TLS complète en interne et n'expose pas les octets bruts du certificat de la même manière. En effectuant une connexion TCP brute et en envoyant un ClientHello façonné manuellement, nous pouvons intercepter la réponse du serveur au niveau des octets.
Un enregistrement TLS ressemble à cela :
┌──────────────────────────────────────────────────┐
│ En-tête d'enregistrement TLS (5 octets) │
│ ┌──────┬──────────┬────────────┐ │
│ │ Type │ Version │ Longueur │ │
│ │ 0x16 │ 0x03 01 │ 2 octets │ │
│ │(Hshk)│(TLS 1.0) │ │ │
│ └──────┴──────────┴────────────┘ │
│ │
│ Message de poignée de main │
│ ┌──────┬────────────┬─────────────────────────┐ │
│ │ Type │ Longueur │ Corps │ │
│ │ 0x01 │ 3 octets │ (ClientHello) │ │
│ │(CHlo)│ │ │ │
│ └──────┴────────────┴─────────────────────────┘ │
└──────────────────────────────────────────────────┘
Le corps du ClientHello contient :
| Champ | Valeur | Objectif |
|---|---|---|
| Version | 0x03 0x03 (TLS 1.2) | Indiquer au serveur que nous parlons TLS 1.2 |
| Aléatoire | Timestamp 4 octets + 28 octets aléatoires | Nonce pour la poignée de main |
| ID de session | 0x00 (vide) | Pas de reprise de session |
| Suites de chiffrement | 9 suites dont TLS_RSA_WITH_AES_128_CBC_SHA | Clé : nous incluons des chiffrements RSA uniquement pour forcer le serveur à utiliser son certificat RSA |
| Compression | 0x00 (aucune) | Requis |
Extensions incluses :
| Extension | ID | Objectif |
|---|---|---|
| SNI (Server Name Indication) | 0x0000 | Indiquer au serveur le nom d'hôte auquel on se connecte |
| Algorithmes de signature | 0x000D | Annoncer les algorithmes de signature supportés |
| Groupes supportés | 0x000A | Courbes elliptiques supportées (P-256, P-384, P-521) |
| Formats de points EC | 0x000B | Points EC non compressés |
[!NOTE] Les suites de chiffrement incluent intentionnellement des chiffrements d'échange de clés RSA (
0x002F=TLS_RSA_WITH_AES_128_CBC_SHA). Cela incite le serveur à répondre avec son certificat RSA plutôt qu'un certificat ECDSA — ce qui est critique car l'exploitation ne fonctionne qu'avec RSA.
Après avoir envoyé le ClientHello, le serveur renvoie plusieurs enregistrements TLS :
Réponse du serveur :
┌─────────────────┐
│ ServerHello │ (type de poignée de main 2)
├─────────────────┤
│ Certificate │ (type de poignée de main 11) ← CECI NOUS INTÉRESSE
├─────────────────┤
│ ServerKeyExchange│ (type de poignée de main 12, optionnel)
├─────────────────┤
│ ServerHelloDone │ (type de poignée de main 14) ← SIGNAL D'ARRÊT
└─────────────────┘
Phase 1 — Supprimer les en-têtes d'enregistrement TLS :
Chaque enregistrement TLS a un en-tête de 5 octets : [type(1)] [version(2)] [longueur(2)]. Le code parcourt tous les enregistrements, et pour ceux avec type == 22 (Handshake), il concatène leurs charges utiles :
while i + 5 <= len(data):
t = data[i] # type de contenu
rl = (data[i + 3] << 8) | data[i + 4] # longueur de l'enregistrement
if t == 22: # Handshake
hs.extend(data[i + 5: i + 5 + rl]) # récupérer la charge utile
i += 5 + rl # enregistrement suivant
Phase 2 — Trouver le message Certificate (type 11) :
Dans le flux de poignée de main, chaque message a un en-tête de 4 octets : [type(1)] [longueur(3)]. Nous recherchons type == 11 :
while j + 4 <= len(hs):
ht = hs[j] # type de poignée de main
hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3] # longueur sur 3 octets
if ht == 11: # Certificate!
# Analyser la liste des certificats à l'intérieur
Phase 3 — Extraire les certificats DER individuels :
Le message Certificate contient une liste de certificats, chacun précédé d'une longueur de 3 octets :
Corps du message Certificate :
┌───────────────────────────────────┐
│ Longueur totale des certificats (3 octets) │
├───────────────────────────────────┤
│ Longueur du certificat 1 (3 octets) │
│ Données DER du certificat 1 (variable) │
├───────────────────────────────────┤
│ Longueur du certificat 2 (3 octets) │
│ Données DER du certificat 2 (variable) │
├───────────────────────────────────┤
│ ... │
└───────────────────────────────────┘
Lors de notre test, nous avons obtenu 3 certificats (certificat feuille, CA intermédiaire, CA racine).
Cette fonction analyse ServerHelloDone (type de poignée de main 14), qui nous indique que le serveur a fini d'envoyer et que nous pouvons arrêter de lire.
Les certificats X.509 sont encodés en DER (Distinguished Encoding Rules), un format binaire basé sur ASN.1 (Abstract Syntax Notation One).