Налицо обычная уязвимость класса PHP-инклудинг. Но, человек, для которого я делал аудит, заявил мне, мол, эту уязвимость эксплуатировать невозможно, поэтому она не считается. Пришлось с ним поспорить
Что такое PHP-include
Проведем маленький ликбез по этой уязвимости. PHP-include — уязвимость которая позволяет «приинклудить» произвольный файл, например такой код:
$module=$_REQUEST["module"]; include("modules/".$module);
И так как в файле «/etc/pаsswd» обычно нет php тегов (), то он выведется в браузер, как вывелся бы html код вынесенный за php теги в обычном php скрипте. Конечно чтение файлов всего лишь одна из возможных реализаций этой атаки. Основная же все таки это инклудинг нужных файлов с нужным php кодом.
Вернемся к примеру. Усложним его:
$module=$_REQUEST["module"]; include("modules/".$module."/module.class.php");
$module = $_REQUEST [ "module" ] ; include ("modules/" . $module . "/module.class.php" ) ; |
Как видите теперь в конце к нашей переменной добавляется строка, которая нам мешает приинклудить любой файл. Так вот, многие функции php не являются бинарно безопасными, т.е такие функции считают NULL-байт за конец строки. Обращаемся к скрипту так:
script.php?module=../../../../../../../../../../../etc/pаsswd%00
И если дирректива magic_quotes отключенна, то мы снова увидим содержимое /etc/pаsswd
Есть ли уязвимость?
Вернемся к нашему коду:
$module=addslashes($_REQUEST["module"]); include("modules/".$module."/module.class.php");
$module = addslashes ($_REQUEST [ "module" ] ) ; include ("modules/" . $module . "/module.class.php" ) ; |
Как видно, наша переменная принудительно проходит через «addslashes» и если мы попытаемся использовать NULL-байт то он будет преобразован в «\0» и инклуда не выйдет.
Но прогресс не стоит на месте! Оказывается некие ребята из USH нашли в PHP интересную фичу PHP filesystem attack vectors (англ.). Если в кратце пересказать суть статьи, то php обрабатывает пути с использованием нескольких особенностей:
- Усечение пути — php обрезает строку пути до заданной длины MAXPATHLEN (В Windows до 270 символов, в NIX — обычно 4096, в BSD — обычно 1024)
- Нормализация пути — php обрабатывает путь специальным образом, удаляя лишние символы «/» и «/.» и их различные комбинации
- Приведение к каноническому виду — убираются лишние переходы, например «dir1/dir2/../dir3» приводится к «dir1/dir3/» при этом существование дирректории «dir2» не проверяется, и прочие похожие преобразования (т.е продолжение нормализации)
Теперь по порядку что происходит с переданным путем:
- Если путь передан относительный, то к нему вначале подставляются значения из диррективы include_path
- Далее путь обрезается до определенной длины в зависимости от платформы
- Проводится нормализация пути
- Путь приводится к каноническому виду
Теперь попробуем воспользоваться этим. Попробуем приинклудить некий файл «test.php» который находится в дирректории «modules/». Для этого добавляем в конец симолы «/.» таким образом чтобы общая длина, вместе с именем файла, значением из include_path была заведомо больше 4096 символов.
script.php?module=test.php/././.[...]/././.
При этом необходимо подгадать так, чтобы вся строка пути (уже обрезанная) заканчивалась на точку (важно!), а не на слеш. Для этого можно добавить один слеш вот так:
И один из этих вариантов сработает точно.
Анализируем
Смотрим по порядку какие преобразования произойдут с путем
modules/test.php//././.[...]/./././module.class.php
4200 символов
Первое что происходит со строкой, это к ней добавляется значение из include_path:
/home/site/public_html/modules/test.php//././.[...]/./././module.class.php
4223 символа
Затем строка ускается до MAXPATHLEN (допустим 4096):
/home/site/public_html/modules/test.php//././.[...]/./.
4096 символов
Здесь видно зачем нужно было добавлять еще один слеш (иначе бы строка обрезалась до слеша). Теперь производится нормализация этой строки, сначала убераются лишние слеши:
/home/site/public_html/modules/test.php/././.[...]/./.
4095 символов
В итоге получаем правильный путь до нужного нам файла, и этот путь уже передастся в инклуд, и приинклудится нужный нам файл.
То есть вот так мы приинклудим наш файл «test.php» успешно.
script.php?module=test.php//././.[...]/././.
А значит уязвимость есть и не теоритическая. В итоге мой клиент проспорил, а я выйграл спор и 10 рублей на которые мы поспорили. Конечно, помимо 10 рублей я выйграл еще и доверие и уважение в глазах клиента, что тоже не мало важно.
Заметки
Здесь я рассмотрю пару интересных особенностей эксплуатации этой уязвимости.
Выход из дирректории
Рассмотрим такой код:
) ;
Опустим тот момент, что можно вопсользоваться RFI и приинклудить файл с удаленного сервера. Допустим на сервере «allow_url_include=OFF».
Рассмотрим ситуацию когда нам надо приинклудить файл из дирректории ниже:
script.php?module=../test.php/././.[...]/././.
Такое обращение выдаст ошибку, типа файл не найден. И для того чтобы это обойти нам надо обратится вот так:
script.php?module=blabla/../../test.php/././.[...]/././.
Я не зря описывал про канонизацию путей. Благодаря ей дирректория «blabla» не обязательно должна существовать.
Добавление просто слешей
Внимательный читатель наверное заметил что в описании нормализации я написал что мол убираются лишние слеши «/» и точки со слешами «/.», так почему бы не использовать просто слеши, дабы избежать лишнего гемора с попаданием точки в конец.
Все дело в алгоритмах, то есть, слеш с точкой «/.» убирается полностью. А вот с просто слешами дело обстоит немного сложнее, при нормализации каждые два слеша заменяются на один до тех пор пока не останется один(!) слеш, пример:
/home/site/public_html/modules/test.php//////////////////
57 символов
↓
/home/site/public_html/modules/test.php/////////
48 символов
↓
/home/site/public_html/modules/test.php/////
44 символов
↓
/home/site/public_html/modules/test.php///
42 символов
↓
/home/site/public_html/modules/test.php//
41 символов
↓
/home/site/public_html/modules/test.php/
40 символов
Небольшое отступление:
Причем если обратить внимание на многие, популярные хак ресурсы, то можно заметить эту ошибку. Я так понимаю эта ошибка началась со статьи некоего Raz0r где он предложил вектор:
index.php?act=../../../../../etc/pаsswd/////[…]/////
И обратите внимание даже журнал ][акер повторил эту ошибку в своей статье . При этом даже в оригинальной статье USH было четко написанно что использовать просто слеши не желательно, и необходимо чтобы в конце перед нормализацией остался символ точки. А просто слеши (даже без точки на конце) работают только в PHP c Suhosin.
То есть использовать слеш с точкой «/.» — более универсальный метод, так как, в отличие от слешей «/», он работает для всех версий php.
Заключение
Надеюсь эта статья поможет вам понять, что в своих скриптах нельзя оставлять даже малейшие уязвимости, так как рано или поздно под них можно разработать свой вектор атаки, что может привести к серьезным последствиям.
Мы поверхностно коснулись темы методов вывода контента в теле шаблона. Давайте теперь подробно разберем что это и с чем его едят. Итак, объявления метода jdoc
присутствуют в каждом
шаблоне Joomla
и выводят в тело шаблона (то бишь на страницу сайта) ту или иную информацию. В целом объявление метода выглядит следующим образом
Данная строчка выводит на сайте информацию из компонентов, например статьи из com_content. Тип элементов вывода указывается в атрибуте.
1. type - типы элементов вывода.
- component - как писал выше, выводит основное содержание страницы. Может вызываться только один раз в шаблоне.
- head
- объявляется так же один раз после открывающего тэга . Служит для вывода стилей, скриптов,
и метаданных текущей страницы. - message - выводит системные сообщения. Объявляется один раз в теле документа (body).
- installation - ничего не выводит и представляет собой «инструкцию» для установки.
- module - выводит на странице единичный модуль. Количество объявлений не ограничено.
- modules - в отличии от предыдущего типа, позволяет выводить в своей позиции не единичое число модулей.
Для первых четырех указанных типов достаточно лишь указать их на странице. В случае с типом модуля задача немного усложняется. Для того, чтобы вывести на странице модуль нам нужно сперва создать для него модульную позицию с уникальным идентификатором (название позиции модуля). Это делается при помощи атрибута name=«имя позиции» и обязательным добавлением строки:
2. style - описание стиля вывода (mod chrome).
От указанного стиля зависит внешний вид и структура оболочки модуля. Выглядит как
:
- xhtml -
выводит модуль в блоке с заголовком function modChrome_xhtml($module, &$params, &$attribs)
{
if (!empty ($module->content)) : ?>
">
showtitle != 0) : ?>
title; ?>
content; ?>
} - table -
выводит модуль в верстке табличной структуры
function modChrome_table($module, &$params, &$attribs)
{ ?>
">
showtitle != 0) : ?>
title; ?>
content; ?>
} - horz -
выводит содержимое модуля в ячейке таблицы, горизонтально function modChrome_horz($module, &$params, &$attribs)
{ ?>
} - rounded
- выводит модуль в нескольких вложенных блоках для сложного стилевого оформления в виде графических границ (напр. закругленных углов) function modChrome_rounded($module, &$params, &$attribs)
{ ?>
">
showtitle != 0) : ?>
title; ?>
content; ?>
} - outline
- добавляет к блоку модуля предустановленные стили css function modChrome_outline($module, &$params, &$attribs)
{
static $css=false;
if (!$css)
{
$css=true;
jimport("joomla.environment.browser");
$doc = JFactory::getDocument();
$browser = JBrowser::getInstance();
$doc->addStyleDeclaration(".mod-preview-info { padding: 2px 4px 2px 4px; border: 1px solid black; position: absolute; background-color: white; color: red;}");
$doc->addStyleDeclaration(".mod-preview-wrapper { background-color:#eee; border: 1px dotted black; color:#700;}");
if ($browser->getBrowser()=="msie")
{
if ($browser->getMajor() <= 7) {
$doc->addStyleDeclaration(".mod-preview-info {filter: alpha(opacity=80);}");
$doc->addStyleDeclaration(".mod-preview-wrapper {filter: alpha(opacity=50);}");
}
else {
$doc->addStyleDeclaration(".mod-preview-info {-ms-filter: alpha(opacity=80);}");
$doc->addStyleDeclaration(".mod-preview-wrapper {-ms-filter: alpha(opacity=50);}");
}
}
else
{
$doc->addStyleDeclaration(".mod-preview-info {opacity: 0.8;}");
$doc->addStyleDeclaration(".mod-preview-wrapper {opacity: 0.5;}");
}
}
?>
position."[".$module->style."]"; ?>
content; ?>
} - none
- аналогично не указанному вообще style. Выводит модуль без оформления и заголовка function modChrome_none($module, &$params, &$attribs)
{
echo $module->content;
}
Все предустановленные стили располагаются в файле templates/system/html/modules.php. Но мы не ограничены использованием только предоставленных вариантов, а вполне можем создавать свои собственные.
3. Создание пользовательского mode chrome.
Итак, предоставленные по умолчанию типы представления модулей не удовлетворяют текущих требований. Нужно добавить свой собственный стиль оформления. В качестве примера выберем достаточно часто повторяющуюся ситуацию. По заданию нужно вместо
поместить заголовок модуля в тэг , который является семантически нейтральным. Так же требуется поместить контентблок модуля в отдельный . Для создания собственного стиля вывода модуля, воспользуемся стандартными средствами. В большинстве шаблонов Joomla существет папка html/ (templates/имя шаблона/html/), используется для так называемой шаблонизации. То есть, если скопировать в эту папку шаблон модуля, то вместо шаблона из директории modules/my_module/tmpl/default будет выводиться файл из templates/имя шаблона/html/my_modules/default. Аналогично шаблонизируются и компоненты. Удобно и практично. В папке html/ Вашего шаблона создадим файл modules.php. Если такой папки в шаблоне нет, то создадим ее. В файл запишем function modChrome_modbox($module, &$params, &$attribs) // Вызываем функцию
{
if (!empty ($module->content)) : /* Проверяем наличие в поиции включенного модуля */?>
">
showtitle != 0) : /* проверяем включен ли заголовок модуля */ ?>
title; /* Выводим заголовок */ ?>
content; /* Выводим содержимое модуля */ ?>
}
?>
Готово. Теперь нужно только указать его в качестве стиля вывода.
Назначаем в нашу позицию модуль и смотрим результат.
Chrome - это конечная обработка html -кода модуля перед его вставкой в главный шаблон сайта. Существуют несколько предопределенных Chrome-стилей (table, horz, xhtml, rounded, outline), но не всегда то что есть подходит для решения текущих задач.
Чтобы определить собственный стиль отображения в шаблоне, нужно создать файл "modules.php" в директории "html". То есть для шаблона с именем "my_template" файл должен располагаться тут - "templates/my_template/html/modules.php".
В этом файле вы должны определить функцию с названием "modChrome_STYLE" где STYLE это имя вашего стиля. Эта функция будет принимать три аргумента - $module, &$params и &$attribs как показано ниже:
function modChrome_STYLE ($module, &$params, &$attribs)
{
/* обработка и вывод html-кода модуля */
}
В этой функции вы можете использовать любой PHP-код, а так же вам будут доступны все параметры самого модуля, его свойства и любые данные сохраненные в базе данных Joomla. В основном бывают нужны только следующее
- $module->content - контент самого модуля, непосредственный html-код.
- $module->title - название модуля, указанное в панели управления в менеджере модулей.
- $module->showtitle - флаг, показывать название или нет (true или false).
Функция modChrome_STYLE - это обыкновенная php-функция, здесь можете использовать абсолютно любой php-код. Ниже приведен пример, если в настройках модуля включено отображение заголовка, то выведется текст заголовка перед контентом модуля.
function modChrome_STYLE ($module, &$params, &$attribs)
{
if ($module->showtitle) {
echo "
" .$module->title ."
";
}
echo $module->content;
}
Есть возможность обратиться к любым параметрам модуля. Например, обрамим модуль классом
">
Так же можно в код позиции добавлять свои атрибуты, которые используются в Chrome. Для этого в тег позиции добавьте собственные атрибуты. Имена дополнительных атрибутов можно указывать произвольные, они все будут передаваться в ассоциативный массив $attribs.
Практический пример Chrome-функции:
function modChrome_custom($module, $params, $attribs) {
if (isset($attribs["headerLevel"]))
{
$headerLevel = $attribs["headerLevel"];
} else {
$headerLevel = 3;
}
if (isset($attribs["background"]))
{
$background = $attribs["background"];
} else {
$background = "blue";
}
echo "
";
if ($module->showtitle)
{
echo "" .$module->title ." ";
}
echo "";
echo $module->content;
echo "";
echo "";
}
Практические примеры использования функции "modChrome_custom"
function modChrome_modbox($module, &$params, &$attribs) // Вызываем функцию
{
if (!empty ($module->content)) : /* Проверяем наличие в поиции включенного модуля */?>
showtitle != 0) : /* проверяем включен ли заголовок модуля */ ?>
title; /* Выводим заголовок */ ?>
content; /* Выводим содержимое модуля */ ?>
}
?> Готово. Теперь нужно только указать его в качестве стиля вывода.
Chrome - это конечная обработка html -кода модуля перед его вставкой в главный шаблон сайта. Существуют несколько предопределенных Chrome-стилей (table, horz, xhtml, rounded, outline), но не всегда то что есть подходит для решения текущих задач.
Чтобы определить собственный стиль отображения в шаблоне, нужно создать файл "modules.php" в директории "html". То есть для шаблона с именем "my_template" файл должен располагаться тут - "templates/my_template/html/modules.php".
В этом файле вы должны определить функцию с названием "modChrome_STYLE" где STYLE это имя вашего стиля. Эта функция будет принимать три аргумента - $module, &$params и &$attribs как показано ниже:
function modChrome_STYLE ($module, &$params, &$attribs) { /* обработка и вывод html-кода модуля */ }
В этой функции вы можете использовать любой PHP-код, а так же вам будут доступны все параметры самого модуля, его свойства и любые данные сохраненные в базе данных Joomla. В основном бывают нужны только следующее
- $module->content - контент самого модуля, непосредственный html-код.
- $module->title - название модуля, указанное в панели управления в менеджере модулей.
- $module->showtitle - флаг, показывать название или нет (true или false).
Функция modChrome_STYLE - это обыкновенная php-функция, здесь можете использовать абсолютно любой php-код. Ниже приведен пример, если в настройках модуля включено отображение заголовка, то выведется текст заголовка перед контентом модуля.
function modChrome_STYLE ($module, &$params, &$attribs) { if ($module->showtitle) { echo "
" .$module->title ."
"; } echo $module->content; }Есть возможность обратиться к любым параметрам модуля. Например, обрамим модуль классом
Так же можно в код позиции добавлять свои атрибуты, которые используются в Chrome. Для этого в тег позиции
Практический пример Chrome-функции:
function modChrome_custom($module, $params, $attribs) { if (isset($attribs["headerLevel"])) { $headerLevel = $attribs["headerLevel"]; } else { $headerLevel = 3; } if (isset($attribs["background"])) { $background = $attribs["background"]; } else { $background = "blue"; } echo "
Практические примеры использования функции "modChrome_custom"