Improper input validation fixed in WordPress Popular Posts plugin.

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