Critical vulnerability fixed in WordPress Pinterest Automatic plugin.

WordPress Pinterest Automatic plugin (7,000+ sales on Envato Market) fixed a critical vulnerability affecting version 4.14.3 and below that could allow unauthenticated users to take over the website and its database.

Unauthenticated Arbitrary WordPress Options Change

CVSS v3.1: 9.8 (Critical)

In the “wp-pinterest-automatic/pinterest-auto.php” main script, WordPress Pinterest Automatic registers the wp_pinterest_automatic_parse_request action with the parse_request hook, i.e., when all query variables for the current request have been parsed:

 940    /**
 941    * custom request for fetch boards
 942    */
 943   function wp_pinterest_automatic_parse_request($wp) {
 944
 945      // only process requests with "my-plugin=ajax-handler"
 946      if (array_key_exists('wp_pinterest_automatic', $wp->query_vars)) {
 947
 948         if($wp->query_vars['wp_pinterest_automatic'] == 'boards'){
 949
 950            require_once('p_core.php');
 951           exit;
 952
 953         }elseif($wp->query_vars['wp_pinterest_automatic'] == 'settings'){
 954
 955            require_once('process_form.php');
 956            exit;
                ...
                ...
                ...
1067    }
1068    add_action('parse_request', 'wp_pinterest_automatic_parse_request');

If the wp_pinterest_automatic query var is set to settings, it loads the “process_form.php” script:

<?php
/**
 * Created with Visual Form Builder by 23rd and Walnut
 * www.visualformbuilder.com
 * www.23andwalnut.com
 */

$form = new ProcessForm();
$form->field_rules = array(
   'field1'=>'required',
   'field3'=>'required',
   'field4'=>'required',
   'field5'=>'required',
   'field6'=>'required'
);
$form->validate();

class ProcessForm
{
   public $field_rules;
   public $error_messages;
   public $fields;
   private $error_list;
   private $is_xhr;





   function __construct()
   {
      $this->error_messages = array(
         'required' => 'This field is required',
         'email' => 'Please enter a valid email address',
         'number' => 'Please enter a numeric value',
         'url' => 'Please enter a valid URL',
         'pattern' => 'Please correct this value',
         'min' => 'Please enter a value larger than $1',
         'max' => 'Please enter a value smaller than $1'
      );

      $this->field_rules = array();
      $this->error_list = '';
      $this->fields = $_POST;
      $this->is_xhr = $this->xhr();
   }





   function validate()
   {
      if (!empty($this->fields))
      {
         //Validate each of the fields
         foreach ($this->field_rules as $field => $rules)
         {
            $rules = explode('|', $rules);

            foreach ($rules as $rule)
            {
               $result = null;

               if (isset($this->fields[$field]))
               {
                  $param = false;

                  if (preg_match("/(.*?)\[(.*?)\]/", $rule, $match))
                  {
                     $rule = $match[1];
                     $param = $match[2];
                  }

                  $this->fields[$field] = $this->clean($this->fields[$field]);

                  //if the field is a checkbox group create string
                  if (is_array($this->fields[$field]))
                     $this->fields[$field] = implode(', ', $this->fields[$field]);

                  // Call the function that corresponds to the rule
                  if (!empty($rule))
                     $result = $this->$rule($this->fields[$field], $param);

                  // Handle errors
                  if ($result === false)
                     $this->set_error($field, $rule);
               }
            }
         }

         if (empty($this->error_list))
         {
            if ($this->is_xhr)
               echo json_encode(array('status' => 'success'));

            $this->process();
         }
         else
         {
            if ($this->is_xhr)
               echo json_encode(array('status' => 'invalid', 'errors' => $this->error_list));
            else echo $this->error_list;
         }
      }
   }





   function process()
   {



      foreach($this->fields as $key => $field){
         update_option( $key, $field);
      }

      if (! is_array($this->fields['wp_pinterest_options'])){

         update_option( 'wp_pinterest_options', array());
      }


   }
   ...
   ...

Line 44, it assigns the POST payload to $this->fields and, in the process method lines 117-119, it takes every key/value pairs found in it and passes them on to the WordPress update_option function.
Neither the wp_pinterest_automatic_parse_request function nor the “process_form.php” script have a capability check to restrict access to that code. They also lack a security nonce.
An unauthenticated user can modify every WordPress options in the database in order, for instance, to create an administrator account or redirect all traffic to an external malicious website among many other possibilities.

Recommendations

Update immediately if you have version 4.14.3 or below installed. If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability.

Timeline

The vulnerability was reported to Envato on August 20, 2021, and a new version 4.14.4 was released on August 23, 2021.

Stay informed about the latest vulnerabilities