Содержание статьи
Баг был обнаружен еще 20 ноября 2017 года исследователем Славко Михайльоски (Slavco Mihajloski) из RIPS Tech, но вплоть до версии 4.9.7, которая вышла 5 июня 2018 года, проблема оставалась незапатченной. То есть на протяжении семи месяцев она представляла серьезную угрозу для безопасности на огромном количестве сайтов по всему миру и на многих из них продолжает представлять.
Стенд
Чтобы разобрать уязвимость, нам сначала понадобится уязвимый WordPress. Первым делом ставим контейнер с базой данных.
$ docker run -d --rm -e MYSQL_USER="wpdel" -e MYSQL_PASSWORD="4hicmMRyUq" -e MYSQL_DATABASE="wpdel" --name=mysql --hostname=mysql mysql/mysql-server
Теперь контейнер, на который поставим веб-сервер. На нем будет располагаться WordPress.
$ docker run -it --rm -p80:80 --name=wpdel --hostname=wpdel --link=mysql debian /bin/bash
Установим все нужные пакеты и расширения PHP.
$ apt-get update && apt-get install -y apache2 php php7.0-mysqli php-gd nano wget
Уязвима версия CMS под номером 4.9.6, ее и скачаем.
$ cd /tmp && wget https://wordpress.org/wordpress-4.9.6.tar.gz
$ tar xzf wordpress-4.9.6.tar.gz
$ rm -rf /var/www/html/* && mv wordpress/* /var/www/html/
$ chown -R www-data:www-data /var/www/html/
Запускаем сервис apache2.
$ service apache2 start
Теперь устанавливаем WordPress, используем наш MySQL-сервер в качестве БД.
Уязвим механизм удаления загруженных файлов, поэтому для успешной эксплуатации юзер должен иметь привилегии на удаление медиа. Создадим такого пользователя.
Анализ уязвимости
В WordPress, как и в любой уважающей себя CMS, можно загружать произвольные файлы и встраивать их в публикуемые посты. Самый очевидный способ применения — это добавление фотографий. Если файл загруженной картинки больше установленных в настройках размеров, то для него создаются миниатюры (thumbnails). Самую мелкую из них можно увидеть при нажатии на кнопку редактирования картинки (в закладке медиа в разделе Thumbnail Settings).
Заглянем в файл post.php
, где находится кусок кода, ответственный за редактирование любой записи в системе. Да, аттачи тоже считаются записями.
/wordpress/wp-admin/post.php
33: if ( $post_id )
34: $post = get_post( $post_id );
...
62: switch($action) {
В переменной $action
находится действие из запроса, которое нужно выполнить. Оператор switch перенаправляет выполнение скрипта в нужную часть кода. Вот, к примеру, запрос на редактирование конкретной записи.
GET /wp-admin/post.php?post=25&action=edit HTTP/1.1
Host: wpdel.visualhack
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,bg;q=0.6,uk;q=0.5,hu;q=0.4
При его обработке исполняется следующий кусок кода:
062: switch($action) {
...
098: case 'edit':
099: $editing = true;
...
122: if ( ! empty( $_GET['get-post-lock'] ) ) {
123: check_admin_referer( 'lock-post_' . $post_id );
124: wp_set_post_lock( $post_id );
125: wp_redirect( get_edit_post_link( $post_id, 'url' ) );
126: exit();
127: }
128:
129: $post_type = $post->post_type;
130: if ( 'post' == $post_type ) {
131: $parent_file = "edit.php";
132: $submenu_file = "edit.php";
133: $post_new_file = "post-new.php";
134: } elseif ( 'attachment' == $post_type ) {
135: $parent_file = 'upload.php';
136: $submenu_file = 'upload.php';
137: $post_new_file = 'media-new.php';
138: } else {
139: if ( isset( $post_type_object ) && $post_type_object->show_in_menu && $post_type_object->show_in_menu !== true )
140: $parent_file = $post_type_object->show_in_menu;
141: else
142: $parent_file = "edit.php?post_type=$post_type";
143: $submenu_file = "edit.php?post_type=$post_type";
144: $post_new_file = "post-new.php?post_type=$post_type";
145: }
146:
...
174: include( ABSPATH . 'wp-admin/edit-form-advanced.php' );
175:
176: break;
Погуляв немного по этой ветке кода, можно обнаружить довольно любопытный экшен.
178: case 'editattachment':
179: check_admin_referer('update-post_' . $post_id);
180:
181: // Don’t let these be changed
182: unset($_POST['guid']);
183: $_POST['post_type'] = 'attachment';
184:
185: // Update the thumbnail filename
186: $newmeta = wp_get_attachment_metadata( $post_id, true );
187: $newmeta['thumb'] = $_POST['thumb'];
188:
189: wp_update_attachment_metadata( $post_id, $newmeta );
Особенно интересен раздел Update the thumbnail filename. Переменная $newmeta
содержит метаданные записи с указанным ID. Ключ thumb
содержит путь до миниатюры, и его значение можно изменить с помощью параметра thumb
в POST-запросе. Эти данные уходят в функцию wp_update_attachment_metadata
, которая расположилась чуть ниже в этом же файле.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»