Palo Alto Networks PAN-OS contém um bypass de autenticação causado por falhas no portal e gateway GlobalProtect, permitindo que invasores estabeleçam conexões VPN não autorizadas, o exploit requer acesso de rede ao portal ou gateway.
Este exploit obtém acesso VPN não autenticado a um gateway/portal Palo Alto GlobalProtect ao forjar um cookie de autenticação usando apenas o certificado TLS disponível publicamente do servidor.
O GlobalProtect usa um cookie de pré-autenticação (portal-userauthcookie) para permitir que os clientes se autentiquem. Aqui está a falha fatal:
Normal Flow:
1. Client authenticates (username + password)
2. Server generates a cookie → encrypts it with server's RSA PUBLIC key
3. Client stores the encrypted cookie
4. On reconnect, client sends the cookie → server decrypts with PRIVATE key → trusts it
The Bug:
The server ONLY checks if the cookie decrypts successfully with its private key.
It does NOT verify WHO encrypted it or if the plaintext content is legitimate.
Como a chave pública RSA está embutida no certificado TLS do servidor (acessível publicamente a qualquer um que se conecte), qualquer atacante pode:
Esta é uma falha de autenticação quebrada clássica — usando criptografia onde uma assinatura digital ou HMAC era necessária.
flowchart TD
A["Step 1: Raw TCP Connect"] --> B["Step 2: Send Crafted TLS ClientHello"]
B --> C["Step 3: Parse ServerHello → Extract DER Certificates"]
C --> D["Step 4: Walk ASN.1 to Extract RSA Public Key"]
D --> E["Step 5: Forge PKCS#1 v1.5 Encrypted Cookie"]
E --> F["Step 6: POST to /ssl-vpn/login.esp"]
F --> G{"Server Decrypts Cookie"}
G -->|"Valid plaintext"| H[" Auth Bypass — VPN Access Granted"]
G -->|"Invalid"| I[" Rejected"]
Por que bruto? Precisamos do certificado do servidor no formato DER (binário bruto). O módulo
ssldo Python completa o handshake TLS completo internamente e não expõe os bytes brutos do certificado da mesma forma. Ao fazer uma conexão TCP bruta e enviar um ClientHello artesanal, podemos interceptar a resposta do servidor no nível de byte.
┌──────────────────────────────────────────────────┐
│ TLS Record Header (5 bytes) │
│ ┌──────┬──────────┬────────────┐ │
│ │ Type │ Version │ Length │ │
│ │ 0x16 │ 0x03 01 │ 2 bytes │ │
│ │(Hshk)│(TLS 1.0) │ │ │
│ └──────┴──────────┴────────────┘ │
│ │
│ Handshake Message │
│ ┌──────┬────────────┬─────────────────────────┐ │
│ │ Type │ Length │ Body │ │
│ │ 0x01 │ 3 bytes │ (ClientHello) │ │
│ │(CHlo)│ │ │ │
│ └──────┴────────────┴─────────────────────────┘ │
└──────────────────────────────────────────────────┘
O corpo do ClientHello contém:
| Campo | Valor | Propósito |
|---|---|---|
| Version | 0x03 0x03 (TLS 1.2) | Informa ao servidor que falamos TLS 1.2 |
| Random | 4-byte timestamp + 28 random bytes | Nonce para o handshake |
| Session ID | 0x00 (vazio) | Sem retomada de sessão |
| Cipher Suites | 9 suites incluindo TLS_RSA_WITH_AES_128_CBC_SHA | Chave: incluímos cifras apenas RSA para forçar o servidor a usar seu certificado RSA |
| Compression | 0x00 (nenhuma) | Obrigatório |
Extensões incluídas:
| Extensão | ID | Propósito |
|---|---|---|
| SNI (Indicação de Nome do Servidor) | 0x0000 | Diz ao servidor qual nome de host estamos conectando |
| Algoritmos de Assinatura | 0x000D | Anuncia quais algoritmos de assinatura suportamos |
| Grupos Suportados | 0x000A | Curvas EC que suportamos (P-256, P-384, P-521) |
| Formatos de Pontos EC | 0x000B | Pontos EC não comprimidos |
[!NOTE] As suites de cifras incluem intencionalmente cifras de troca de chaves RSA (
0x002F=TLS_RSA_WITH_AES_128_CBC_SHA). Isso incentiva o servidor a responder com seu certificado RSA em vez de um ECDSA — o que é crítico porque o exploit só funciona com RSA.
Server Response:
┌─────────────────┐
│ ServerHello │ (handshake type 2)
├─────────────────┤
│ Certificate │ (handshake type 11) ← WE WANT THIS
├─────────────────┤
│ ServerKeyExchange│ (handshake type 12, optional)
├─────────────────┤
│ ServerHelloDone │ (handshake type 14) ← STOP SIGNAL
└─────────────────┘
Fase 1 — Remover cabeçalhos de registros TLS:
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]) # grab payload
i += 5 + rl # next record
Fase 2 — Encontrar a mensagem Certificate (tipo 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 length
if ht == 11: # Certificate!
# Parse the certificate list inside
Fase 3 — Extrair certificados DER individuais:
Certificate Message Body:
┌───────────────────────────────────┐
│ Total Certs Length (3 bytes) │
├───────────────────────────────────┤
│ Cert 1 Length (3 bytes) │
│ Cert 1 DER data (variable) │
├───────────────────────────────────┤
│ Cert 2 Length (3 bytes) │
│ Cert 2 DER data (variable) │
├───────────────────────────────────┤
│ ... │
└───────────────────────────────────┘
Em nosso teste, obtivemos 3 certificados (certificado folha, CA intermediária, CA raiz).
Esta função procura por ServerHelloDone (tipo de handshake 14), que nos informa que o servidor terminou de enviar e podemos parar de ler.
Os certificados X.509 são codificados em DER (Regras de Codificação Distintas), que é um formato binário baseado em ASN.1 (Notação de Sintaxe Abstrata Um).
┌─────┬────────┬───────────────────┐
│ Tag │ Length │ Value (payload) │
│ 1B │ 1-5B │ variable │
└─────┴────────┴───────────────────┘
Codificação de comprimento:
0x80: o comprimento é aquele byte diretamente (forma curta)0x80: bits baixos 7 = número de bytes seguintes que codificam o comprimento (forma longa)# Example: length byte = 0x82 → 2 more bytes follow
# Next 2 bytes: 0x06 0x4F → length = 0x064F = 1615 bytes
def rd_tl(d, p):
tag = d[p]; p += 1
length = d[p]; p += 1
if length & 0x80: # long form?
nb = length & 0x7F # how many bytes follow
length = 0
for _ in range(nb):
length = (length << 8) | d[p]
p += 1
return {"tag": tag, "len": length, "pos": p} # pos = start of value