Показать сообщение отдельно

  #3  
Старый 16.08.2020, 21:13
crlf
Guest
Сообщений: n/a
Провел на форуме:
169212

Репутация: 441
По умолчанию

Пользуясь случаем, рассмотрим применение кейса на реальном примере, который был раскручен с его помощью до RFI (Remote File Include).

Для локального воспроизведения нам понадобится WordPress 5.5, а так же плагин WP-Live Chat by 3CX версии 9.0.17, который на текущий момент имеет 50 тысяч активных установок.



Запускать всё это дело будем на стенде с Debian 9, веб-сервером Apache/2.4.25 и PHP 7.3.15 с включенным выводом ошибок (display_errors = On).





После нехитрой установки WP и плагина, вычищаем все админские куки и следуем на главную страницу нашего новоиспечённого блога.



Следующим шагом, проверяем теорию описанную в первом посте, при помощи простейшего теста, с подставлением GET параметра "?this=1".



Получаем фатальную ошибку, с раскрытием локальных путей, а так же номером строки где она была вызвана. Далее открываем файл в любимом текстовом редакторе и находим это место. Наш кейс всплывает в функии evaluate_php_template:

./wp-live-chat-support/includes/helpers/utils_helper.php:

PHP код:
PHP:
[
COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]. . .
public static function[/COLOR][COLOR="#0000BB"]evaluate_php_template[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$path[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$args[/COLOR][COLOR="#007700"]) {
foreach ([/COLOR][COLOR="#0000BB"]$args[/COLOR][COLOR="#007700"]as[/COLOR][COLOR="#0000BB"]$key[/COLOR][COLOR="#007700"]=>[/COLOR][COLOR="#0000BB"]$value[/COLOR][COLOR="#007700"]) {
${[/COLOR][COLOR="#0000BB"]$key[/COLOR][COLOR="#007700"]} =[/COLOR][COLOR="#0000BB"]$value[/COLOR][COLOR="#007700"];
}

[/
COLOR][COLOR="#0000BB"]ob_start[/COLOR][COLOR="#007700"]();
include([/COLOR][COLOR="#0000BB"]$path[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$var[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]ob_get_contents[/COLOR][COLOR="#007700"]();
[/
COLOR][COLOR="#0000BB"]ob_end_clean[/COLOR][COLOR="#007700"]();

return[/COLOR][COLOR="#0000BB"]$var[/COLOR][COLOR="#007700"];
}
.
. .
[/
COLOR][/COLOR
, где невооружённым глазом видно теоретическую возможность удалённого инклуда.

Если скрипт упал в этом месте, значит пользовательский ввод попадает в $args. Для того чтобы убедиться в этом, поищем в коде плагина места где используется эта функия и в свою очередь обнаружим другую функцию - load_view:

./wp-live-chat-support/includes/wplc_base_controller.php:

PHP код:
PHP:
[
COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]. . .
protected function[/COLOR][COLOR="#0000BB"]load_view[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$filepath[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$return_html[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]false[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$add_wrapper[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$children[/COLOR][COLOR="#007700"]= array() ) {
[/
COLOR][COLOR="#0000BB"]$data[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]convert_view_data[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]view_data[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$data[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]"page_title"[/COLOR][COLOR="#007700"]] =[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]page_title[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]$view_data[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]array_merge[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$data[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$view_data[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'wplc_settings'[/COLOR][COLOR="#007700"]] =[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]wplc_settings[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]$view_data[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'selected_action'[/COLOR][COLOR="#007700"]] =[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]selected_action[/COLOR][COLOR="#007700"];
unset([/COLOR][COLOR="#0000BB"]$data[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$data_literal[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]generate_wrapper_data[/COLOR][COLOR="#007700"]();
[/
COLOR][COLOR="#0000BB"]$view_html[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]TCXUtilsHelper[/COLOR][COLOR="#007700"]::[/COLOR][COLOR="#0000BB"]evaluate_php_template[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$filepath[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$view_data[/COLOR][COLOR="#007700"]);
if([/COLOR][COLOR="#0000BB"]$add_wrapper[/COLOR][COLOR="#007700"]) {
[/
COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"].=[/COLOR][COLOR="#0000BB"]$view_html[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"].=[/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"];
}else
{
[/
COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$view_html[/COLOR][COLOR="#007700"];
}
if ([/COLOR][COLOR="#0000BB"]count[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$children[/COLOR][COLOR="#007700"]) >[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]) {
[/
COLOR][COLOR="#0000BB"]libxml_use_internal_errors[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"]= new[/COLOR][COLOR="#0000BB"]DOMDocument[/COLOR][COLOR="#007700"]();
[/
COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]formatOutput[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"];
[/
COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]loadHTML[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"]);
foreach ([/COLOR][COLOR="#0000BB"]$children[/COLOR][COLOR="#007700"]as[/COLOR][COLOR="#0000BB"]$child[/COLOR][COLOR="#007700"]) {
[/
COLOR][COLOR="#0000BB"]$container_element[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]getElementById[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$child[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]id[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$html[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$child[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]controller[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]view[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]false[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$node[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$this[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]createElementFromHTML[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$html[/COLOR][COLOR="#007700"]);
[/
COLOR][COLOR="#0000BB"]$container_element[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]appendChild[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$node[/COLOR][COLOR="#007700"]);
}
[/
COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]$doc[/COLOR][COLOR="#007700"]->[/COLOR][COLOR="#0000BB"]saveHTML[/COLOR][COLOR="#007700"]();
}
if ([/COLOR][COLOR="#0000BB"]$return_html[/COLOR][COLOR="#007700"]) {
return[/COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"];
} else {
echo[/COLOR][COLOR="#0000BB"]$result_view[/COLOR][COLOR="#007700"];
return[/COLOR][COLOR="#0000BB"]true[/COLOR][COLOR="#007700"];
}
}

.
. .
[/
COLOR][/COLOR
На третьей строке этой функции видим, что переменная $data объединяется с массивом $_GET. Это как раз то место, когда пользовательские данные без какой-либо обработки попадают в уязвимую функцию evaluate_php_template. А это значит, мы можем изменить переменную $path на любое значение. И сделав запрос вида http://wordpress/?path=/etc/passwd, убеждаемся в этом:



Или так:





Теперь давайте разбираться как так произошло







На первом скрине видим, что load_view() в плагине используется повсеместно, для отображения различных страниц темплейтов, с предустановленными зарание переменными и юзеринпутом. Важным аргументом для неё является $filepath, который, как видно, при вызове жёстко прописывается и повлиять на него никак нельзя.

Далее, подготавливаются некие переменные, для подключаемого шаблона, и массив с этими переменными объединяется с $_GET. И через несколько строк, $view_data, с пользовательскими гет данными, отправляется в функцию evaluate_php_template(), куда так же первым аргументом передаётся захардкоженный $filepath.

В следующем методе аргументы $args, возможностями переменных переменных глобализуются или переназначаются (неявно) в контексте нашей функции, вероятно, для заполнения подключаемого темплейта.

Всё бы ничего, но мы передав ?path=/etc/passwd сделали так, что один из ключей перебираемого массива $args является "path", что переназначит значение, казалось бы жёстко прописанной, переменной $path переданой заранее!

$this is the end ​
 
Ответить с цитированием