Palo Alto Networks PAN-OS уязвим к обходу аутентификации, вызванному ошибками в портале и шлюзе GlobalProtect, что позволяет злоумышленникам устанавливать несанкционированные VPN-подключения; эксплуатация требует сетевого доступа к порталу или шлюзу.
Этот эксплойт обеспечивает неаутентифицированный доступ к VPN к шлюзу/порталу Palo Alto GlobalProtect, подделывая cookie аутентификации с использованием только публично доступного TLS-сертификата сервера.
GlobalProtect использует предварительную cookie аутентификации (portal-userauthcookie) для аутентификации клиентов. Вот фатальный недостаток:
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.
Поскольку открытый ключ RSA встроен в TLS-сертификат сервера (публично доступный любому, кто подключается), любой злоумышленник может:
Это классический недостаток сломанной аутентификации — использование шифрования там, где требовалась цифровая подпись или HMAC.
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"]
Почему именно raw? Нам нужен сертификат сервера в формате DER (сырые двоичные данные). Модуль Python
sslвыполняет полное TLS-рукопожатие внутри себя и не отдаёт сырые байты сертификата таким же образом. Выполняя raw TCP-подключение и отправляя созданное вручную ClientHello, мы можем перехватить ответ сервера на уровне байтов.
TLS-запись выглядит так:
┌──────────────────────────────────────────────────┐
│ 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)│ │ │ │
│ └──────┴────────────┴─────────────────────────┘ │
└──────────────────────────────────────────────────┘
Тело ClientHello содержит:
| Поле | Значение | Назначение |
|---|---|---|
| Version | 0x03 0x03 (TLS 1.2) | Сообщаем серверу, что мы используем TLS 1.2 |
| Random | 4-байтовая метка времени + 28 случайных байт | Nonce для рукопожатия |
| Session ID | 0x00 (пусто) | Без возобновления сессии |
| Cipher Suites | 9 наборов, включая TLS_RSA_WITH_AES_128_CBC_SHA | Ключевой момент: мы включаем RSA-шифры, чтобы заставить сервер использовать свой RSA-сертификат |
| Compression | 0x00 (нет) | Обязательно |
Включенные расширения:
| Расширение | ID | Назначение |
|---|---|---|
| SNI (Server Name Indication) | 0x0000 | Сообщаем серверу, к какому имени хоста мы подключаемся |
| Signature Algorithms | 0x000D | Объявляем, какие алгоритмы подписи мы поддерживаем |
| Supported Groups | 0x000A | Эллиптические кривые, которые мы поддерживаем (P-256, P-384, P-521) |
| EC Point Formats | 0x000B | Несжатые точки эллиптических кривых |
[!NOTE] Наборы шифров намеренно включают шифры RSA-обмена ключами (
0x002F=TLS_RSA_WITH_AES_128_CBC_SHA). Это подталкивает сервер отвечать RSA-сертификатом, а не ECDSA, — что критически важно, поскольку эксплойт работает только с RSA.
После отправки ClientHello сервер отправляет обратно несколько TLS-записей:
Server Response:
┌─────────────────┐
│ ServerHello │ (handshake type 2)
├─────────────────┤
│ Certificate │ (handshake type 11) ← WE WANT THIS
├─────────────────┤
│ ServerKeyExchange│ (handshake type 12, optional)
├─────────────────┤
│ ServerHelloDone │ (handshake type 14) ← STOP SIGNAL
└─────────────────┘
Фаза 1 — удаление заголовков TLS-записей:
Каждая TLS-запись имеет 5-байтовый заголовок: [type(1)] [version(2)] [length(2)]. Код сканирует все записи, и для любой с type == 22 (Handshake) объединяет их полезные данные:
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
Фаза 2 — поиск сообщения Certificate (type 11):
Внутри потока handshake каждое сообщение имеет 4-байтовый заголовок: [type(1)] [length(3)]. Мы ищем 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 length
if ht == 11: # Certificate!
# Parse the certificate list inside
Фаза 3 — извлечение отдельных DER-сертификатов:
Сообщение Certificate содержит список сертификатов, каждый из которых предваряется 3-байтовым полем длины:
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) │
├───────────────────────────────────┤
│ ... │
└───────────────────────────────────┘
В нашем тестовом запуске мы получили 3 сертификата (конечный сертификат, промежуточный CA, корневой CA).
Эта функция ищет ServerHelloDone (тип handshake 14), который сообщает нам, что сервер закончил отправку, и мы можем прекратить чтение.
X.509-сертификаты закодированы в DER (Distinguished Encoding Rules), который представляет собой двоичный формат на основе ASN.1 (Abstract Syntax Notation One).
Каждый элемент в DER выглядит так:
┌─────┬────────┬───────────────────┐
│ Tag │ Length │ Value (payload) │
│ 1B │ 1-5B │ variable │
└─────┴────────┴───────────────────┘
Кодирование длины:
0x80: длина равна самому байту (короткая форма)0x80: младшие 7 бит = количество последующих байт, которые кодируют длину (длинная форма)# Example: length byte = 0x82 → 2 more bytes follow
# Next 2 bytes: 0x06 0x4F → length = 0x064F = 1615 bytes