Открытка для WordPress. Захватываем контроль над сайтом, спрятав код в картинке

0
11

Содержание статьи

Сегодня я расскажу об уязвимости, дающей возможность исполнять произвольный код в самой популярной 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. В итоге все это добро попадает в эту часть кода:

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

Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.

Подпишись на «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here

55 + = 65