Interesting Arbitrary File Upload Vulnerability Patched in User Registration WordPress Plugin
On June 19, 2023, the Wordfence Threat Intelligence team identified and began the responsible disclosure process for an Arbitrary File Upload vulnerability in WPEverest’s User Registration plugin, which is actively installed on more than 60,000 WordPress websites. This vulnerability makes it possible for an authenticated attacker with minimal permissions, such as a subscriber, to upload arbitrary files, including PHP files, and achieve remote code execution on a vulnerable site’s server.
Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule to protect against any exploits targeting this vulnerability on June 20, 2023. Sites still using the free version of Wordfence will receive the same protection on July 20, 2023.
We contacted WPEverest on June 19, 2023, and received a response the same day. After we provided full disclosure details, the developer released the first patch, which did not fully address the vulnerability, in version 3.0.2 on June 29, 2023. A fully patched version, 3.0.2.1, was released on July 4, 2023. We would like to commend the WPEverest development team for their prompt response and timely patch.
We urge users to update their sites with the latest patched version of User Registration, which is version 3.0.2.1 at the time of this writing, as soon as possible.
Vulnerability Summary from Wordfence Intelligence
Description: User Registration $file_path,
‘file_extension’=> $file_extension,
);
$attachment_id=wp_rand();
ur_clean_tmp_files();
$url=UR_UPLOAD_URL . ‘temp-uploads/’ . sanitize_file_name( $file_name );
wp_send_json_success(
array(
‘attachment_id’=> $attachment_id,
‘upload_files’ => crypt_the_string( maybe_serialize( $files ), ‘e’ ),
‘url’ => $url,
)
);
}
JSON response after the file upload in the profile_pic_upload() function
The encrypted upload_files
data in the response is something like this:
Encryption is used due to the way the plugin handles uploads because the encrypted data is decrypted and used to determine the filename and filepath where the file is saved for the user. However, for data to be encrypted and decrypted, an encryption key is required. As a general rule, encryption keys should be confidential and unique to each website.
/**
* Encrypt/Decrypt the provided string.
* Encrypt while setting token and updating to database, decrypt while comparing the stored token.
*
* @param string $string String to encrypt/decrypt.
* @param string $action Encrypt/decrypt action. 'e' for encrypt and 'd' for decrypt.
* @return string Encrypted/Decrypted string.
*/
function crypt_the_string( $string, $action='e' ) {
$secret_key='ur_secret_key';
$secret_iv ='ur_secret_iv';
$output =false;
$encrypt_method='AES-256-CBC';
$key =hash( 'sha256', $secret_key );
$iv =substr( hash( 'sha256', $secret_iv ), 0, 16 );
if ( 'e'==$action ) {
if ( function_exists( 'openssl_encrypt' ) ) {
$output=base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
} else {
$output=base64_encode( $string );
}
} elseif ( 'd'==$action ) {
if ( function_exists( 'openssl_decrypt' ) ) {
$output=openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
} else {
$output=base64_decode( $string );
}
}
return $output;
}
We unfortunately found that the encryption key is hardcoded in vulnerable versions of the plugin in the crypt_the_string()
function, which means that threat actors also had access to the key which was not unique per WordPress installation. This makes it possible for attackers to craft an uploaded files data array payload that can be used to modify the filename, path, and extension when saving the profile picture.
The plugin calls the function ur_upload_profile_pic()
when saving the profile, which contains the following code:
$upload=maybe_unserialize( crypt_the_string( $upload_file, 'd' ) );
if ( isset( $upload['file_name'] ) && isset( $upload['file_path'] ) && isset( $upload['file_extension'] ) ) {
$upload_path=$upload_path . '/';
$file_name =wp_unique_filename( $upload_path, $upload['file_name'] );
$file_path =$upload_path . sanitize_file_name( $file_name );
// Check the type of file. We'll use this as the 'post_mime_type'.
$filetype=wp_check_filetype( basename( $file_name ), null );
$moved =rename( $upload['file_path'], $file_path );
if ( $moved ) {
$attachment_id=wp_insert_attachment(
array(
'guid' => $file_path,
'post_mime_type'=> $filetype['type'],
'post_title' => preg_replace( '/.[^.]+$/', '', sanitize_file_name( $file_name ) ),
'post_content' => '',
'post_status' => 'inherit',
),
$file_path
);
if ( ! is_wp_error( $attachment_id ) ) {
include_once ABSPATH . 'wp-admin/includes/image.php';
// Generate and save the attachment metas into the database.
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file_path ) );
}
}
}
Rename and move the file in ur_upload_profile_pic() function
The function decrypts the encrypted file data, which is specified in the save request. Based on this data, the file in the temp folder is moved and renamed using the rename()
php function. Unfortunately, however, there is no file type check before it, which means that the image file can be renamed to a file with any type of extension, such as .php, .phtml, .html, and more.
Exploit Possibilities
Exploiting the vulnerability requires multiple complex steps, as two separate functions and requests must be used to upload and move the file:
- Register a user
- Log in as the user (since it is only possible to upload a profile picture for the user)
- Upload the malicious exploit.png image file
- Retrieve the encrypted file data from the response
- Decrypt the file data
- Modify the file extension to php in the file name
- Encrypt the file data
- Save the profile with the encrypted and modified file data
Since it is only possible to upload an image file during the upload process, because its extension is checked, the initial step involves uploading a file named, for example, exploit.png as a profile picture with the following request:
In this scenario, the attacker uploads an exploit.png file, which is actually a PHP script, but with a .png extension:
Source: wordfence.com