Сегодня я расскажу об уязвимости, дающей возможность исполнять произвольный код в самой популярной CMS в мире — WordPress. Причина бага — в недостаточной фильтрации метаданных загруженного файла, что дает возможность выйти из директории, используя некорректную логику при кадрировании картинок. Злоумышленник может загрузить произвольный PHP-код в теле изображения и поместить его в папку, откуда будет возможен вызов.

 

Используемые уязвимости

  • CVE-2019-8942 — уязвимость заключается в возможности свободного манипулирования метаданными записей блога, а именно ключом _wp_attached_file, который отвечает за путь загруженного аттача.
  • CVE-2019-8943 — из-за некорректной логики функции wp_crop_image атакующий, используя конструкцию вида /valid/image/path.jpg?/../../path/traversal, может выйти из директории, предназначенной для хранения пользовательских файлов, и записать файл в произвольный путь.

Проблему обнаружили исследователи из RIPS Technologies еще в октябре прошлого (2018-го) года. Оригинальный отчет об этом был представлен Саймоном Сканнеллом (Simon Scannell) 19 февраля и содержит общее описание обнаруженных багов, варианты их эксплуатации и видео с PoC.

Мы же детально пройдемся по всем этапам эксплуатации и разберемся в проблеме. Поехали!

 

Стенд

Для демонстрации уязвимости я, как всегда, воспользуюсь докер-контейнерами для поднятия тестового окружения.

Сначала база данных. Я возьму привычный MySQL.

$ docker run -d --rm -e MYSQL_USER="wprce" -e MYSQL_PASSWORD="QJmfdGjW47" -e MYSQL_DATABASE="wprce" --name=wpmysql --hostname=mysql mysql/mysql-server

Теперь дело за контейнером с WordPress.

$ docker run -it --rm -p80:80 --name=wprce --hostname=wprce --link=wpmysql debian /bin/bash

Не забывай слинковать его с контейнером базы данных. Далее устанавливаем требуемые пакеты, среди них, разумеется, веб-сервер Apache и PHP.

$ apt-get update && apt-get install -y apache2 php php7.0-mysqli php-imagick php-xdebug nano wget build-essential checkinstall

Обрати внимание на пакет php-imagick. Уязвимость связана с обработкой картинок, для чего частенько используется расширение GD, но сегодня особый случай и нам нужен ImageMagick. Подробнее об этом я расскажу, говоря об эксплуатации.

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

$ cd /tmp && wget https://wordpress.org/wordpress-5.0.tar.gz

Распаковываем архив в веб-рут.

$ tar xzf wordpress-5.0.tar.gz
$ rm -rf /var/www/html/* && mv wordpress/* /var/www/html/
$ chown -R www-data:www-data /var/www/html/

Если хочешь дебажить приложение вместе со мной, то настраивай удаленную отладку в Xdebug. Я буду использовать в качестве дебаггера PHPStorm.

$ echo "xdebug.remote_enable=1" >> /etc/php/7.0/apache2/conf.d/20-xdebug.ini
$ echo "xdebug.remote_host=192.168.99.1" >> /etc/php/7.0/apache2/conf.d/20-xdebug.ini

Наконец-то запускаем сам сервер и инсталлируем WordPress.

$ service apache2 start
Установка WordPress 5.0
Установка WordPress 5.0

После этого не забудь отключить автообновление CMS на всякий случай.

$ echo "define( 'WP_AUTO_UPDATE_CORE', false );" >> /var/www/html/wp-config.php
 

Манипулируем метаданными, или CVE-2019-8942

Проэксплуатировать уязвимость можно только от имени пользователей, которым разрешена загрузка медиафайлов. Роль author вполне подойдет для этих целей, поэтому создадим нового пользователя с такими правами.

Теперь немного о загрузках медиафайлов. Помимо того что файл физически помещается в директорию wp-content/uploads, в процессе загрузки его метаданные заносятся в таблицу wp_postmeta. Для CMS нет особой разницы между записями, страницами и файлами, для системы все это объекты типа WP_Post, и различаются они метаданными, атрибутом post_type и прочим.

/wp-includes/class-wp-post.php
022: final class WP_Post {
...
186:    /**
187:     * The post’s type, like post or page.
...
192:    public $post_type = 'post';
/wp-includes/post.php
20: function create_initial_post_types() {
21:     register_post_type( 'post', array(
...
41:     register_post_type( 'page', array(
42:         'labels' => array(
...
62:     register_post_type( 'attachment', array(
63:         'labels' => array(

Загрузим рандомную картинку и заглянем в базу данных.

Метаданные загруженного файла в таблице wp_postmeta
Метаданные загруженного файла в таблице wp_postmeta

Ключ _wp_attachment_metadata содержит сериализованный объект, где располагается вся информация о загруженной картинке, которая может понадобиться WordPress. Главная проблема в том, что злоумышленник может перезаписать любые метаданные произвольными.

Как мы выяснили, загруженный файл в WordPress является экземпляром Post. Поэтому за добавление и обновление данных о нем отвечает один и тот же метод — wp_insert_post. Только в первом случае он почти сразу вызывается из функции wp_insert_attachment.

/wp-includes/post.php
5068: function wp_insert_attachment( $args, $file = false, $parent = 0, $wp_error = false ) {
5069:   $defaults = array(
5070:       'file'        => $file,
5071:       'post_parent' => 0
5072:   );
5073:
5074:   $data = wp_parse_args( $args, $defaults );
5075:
5076:   if ( ! empty( $parent ) ) {
5077:       $data['post_parent'] = $parent;
5078:   }
5079:
5080:   $data['post_type'] = 'attachment';
5081:
5082:   return wp_insert_post( $data, $wp_error );
5083: }
/wp-includes/post.php
3143: /**
3144:  * Insert or update a post.
3145:  *
...
3203: function wp_insert_post( $postarr, $wp_error = false ) {
3204:   global $wpdb;
3205:
3206:   $user_id = get_current_user_id();

Во втором случае — цепочкой edit_post => wp_update_post => wp_insert_attachment.

/wp-admin/includes/post.php
187: function edit_post( $post_data = null ) {
188:    global $wpdb;
189:
190:    if ( empty($post_data) )
191:        $post_data = &$_POST;
...
377:    $success = wp_update_post( $post_data );
/wp-includes/post.php
3776: function wp_update_post( $postarr = array(), $wp_error = false ) {
3777:   if ( is_object($postarr) ) {
...
3817:   if ($postarr['post_type'] == 'attachment')
3818:       return wp_insert_attachment($postarr);
3819:
3820:   return wp_insert_post( $postarr, $wp_error );
3821: }
Отладка функции редактирования данных загруженного файла
Отладка функции редактирования данных загруженного файла

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

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии