WordPress Advanced Shipment Tracking for WooCommerce fixed critical vulnerability.

The WordPress Advanced Shipment Tracking for WooCommerce plugin (50,000+ active installations) fixed a critical vulnerability affection version 3.2.4.1 and below and, to some extend, version 3.2.6 (see Timeline below for more details).

Authenticated WordPress Options Change

CVSS v3.1: 9.9 (critical)

In the init method of the “woo-advanced-shipment-tracking/includes/class-wc-advanced-shipment-tracking-trackship.php” script, the plugin registers several AJAX actions when the WooCommerce shop is connected to TrackShip ($wc_ast_api_key):

 61   public function init() {   
 62
 63   add_action( 'admin_enqueue_scripts', array( $this, 'trackship_styles' ), 4 );      
 64
 65   $wc_ast_api_key = get_option( 'wc_ast_api_key' ); 
 66   if ( $wc_ast_api_key ) {
         ...
         ...
129      add_action( 'wp_ajax_update_shipment_status_email_status', array( $this, 'update_shipment_status_email_status_fun' ) );
130
131      add_action( 'wp_ajax_update_enable_late_shipments_email', array( $this, 'update_enable_late_shipments_email_fun' ) );
         ...
         ...
147      }
148   }

The update_shipment_status_email_status action loads the update_shipment_status_email_status_fun function, located line 1053:

/*
* update all shipment status email status
*/
public function update_shipment_status_email_status_fun() {	
   $status_settings = get_option( $_POST['settings_data'] ); 
   $status_settings[ $_POST['id'] ] = wc_clean( $_POST[ 'wcast_enable_status_email' ] );
   update_option( $_POST['settings_data'], $status_settings );		
   exit;
}

The function is used to save options to the WordPress options table in the database. It doesn’t validate inputs and as it lacks a capability check and a security nonce, it is accessible to all authenticated users and WooCommerce customers.

Because the function uses an array, $status_settings, it may look like only options stored as an array in the database could be modified. However, in PHP, each character within a string can be accessed and modified by specifying its zero-based offset similarly to an array of characters:

$string = '';
$string[0] = 'f';
$string[1] = 'o';
$string[2] = 'o';

is equivalent to:

$string = 'foo';

Therefore, any array (and integer) data in the WordPress options table can also be altered using such a trick. In the example below I redirected all traffic by changing the siteurl from “https://example.com” to “https://evilsite.com” without too much hassle:

MariaDB [example]> SELECT * FROM `wp_options` LIMIT 5;
+-----------+--------------------+-----------------------------+----------+
| option_id | option_name        | option_value                | autoload |
+-----------+--------------------+-----------------------------+----------+
|         1 | siteurl            | https://evilsite.com        | yes      |
|         2 | home               | https://example.com         | yes      |
|         3 | blogname           | WP Example                  | yes      |
|         4 | blogdescription    | Just another WordPress site | yes      |
|         5 | users_can_register | 0                           | yes      |
+-----------+--------------------+-----------------------------+----------+
5 rows in set (0.001 sec)

Any authenticated users, including WooCommerce customers, can use that AJAX action to modify WordPress options in the database, for instance:

  • Redirect all traffic to an external malicious website (siteurl).
  • Create an administrator account by enabling registration (users_can_register) and setting the default role to “administrator” (default_role).
  • Change the administrator email address (admin_email).
  • Activate/deactivate plugins (active_plugins).
  • Etc.

Another AJAX action, update_enable_late_shipments_email, loads the update_enable_late_shipments_email_fun function located line 1063:

/*
* update late shipment email status
*/
public function update_enable_late_shipments_email_fun() {		
   $status_settings[ $_POST['id'] ] = wc_clean( $_POST[ 'wcast_enable_late_shipments_email' ] );
   update_option( $_POST['settings_data'], $status_settings );			
   exit;
}

Although it is prone to the same vulnerability, this function has a bug: $status_settings isn’t initialized before being used L1064, hence it could be exploited too but only to modify an array of options in the database.

Additional Issues

Several functions in the plugin were accessible to any logged-in users.

Recommendations

Update immediately if you have version 3.2.6 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 the authors on June 23, 2021, and a new version 3.2.5 was released on June 25, 2021. However, as the new version relied on security nonces only, we contacted the developers on June 28 to warn that, despite temporarily solving the problem, nonces could be compromised and thus shouldn’t be used as a substitute for capability checks to protect functions. Version 3.2.6 and 3.2.7 were released on July 22 and 23, 2021.

Stay informed about the latest vulnerabilities