CVE-2026-61952

Bulk Edit Products for WooCommerce – WP Sheet Editor <= 1.8.21 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.8.22
Patched in
3d
Time to patch

Description

The Bulk Edit Products for WooCommerce – WP Sheet Editor plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.8.21. This makes it possible for authenticated attackers, with author-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.8.21
PublishedJuly 12, 2026
Last updatedJuly 14, 2026
Affected pluginwoo-bulk-edit-products

What Changed in the Fix

Changes introduced in v1.8.22

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-61952 - WP Sheet Editor Missing Authorization ## 1. Vulnerability Summary The **Bulk Edit Products for WooCommerce – WP Sheet Editor** plugin (versions <= 1.8.21) contains a missing authorization vulnerability in its AJAX handlers. Specifically, functions rela…

Show full research plan

Exploitation Research Plan: CVE-2026-61952 - WP Sheet Editor Missing Authorization

1. Vulnerability Summary

The Bulk Edit Products for WooCommerce – WP Sheet Editor plugin (versions <= 1.8.21) contains a missing authorization vulnerability in its AJAX handlers. Specifically, functions related to column visibility management (e.g., remove_column, update_columns_settings) lack sufficient capability checks. While restore_columns includes a check for manage_options, other adjacent functions in WP_Sheet_Editor_Columns_Visibility only verify the nonce but fail to restrict the action to administrators. This allows authenticated users with Author-level access to modify global plugin settings, such as hiding columns for all users or altering spreadsheet visibility configurations.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Vulnerable Action: vgse_remove_column (and potentially vgse_update_columns_visibility)
  • Required Authentication: Authenticated, Author-level access (PR:L). Authors generally have access to the WooCommerce Product spreadsheet if they have the edit_products capability.
  • Payload Parameters:
    • action: vgse_remove_column
    • post_type: The post type slug (e.g., product)
    • column: The internal key of the column to remove (e.g., _regular_price, _sku)
    • nonce: A valid security nonce extracted from the editor page.

3. Code Flow

  1. Registration: In modules/columns-visibility/columns-visibility.php, the init() method registers several AJAX actions:
    add_action( 'wp_ajax_vgse_remove_column', array( $this, 'remove_column' ) );
    add_action( 'wp_ajax_vgse_update_columns_visibility', array( $this, 'update_columns_settings' ) );
    
  2. Missing Check: In version 1.8.21, the remove_column() function (and update_columns_settings) likely performs a nonce check using VGSE()->helpers->verify_nonce_from_request() but lacks the secondary capability check seen in restore_columns():
    // Contrast with restore_columns() which HAS the check:
    if ( ! VGSE()->helpers->verify_nonce_from_request() || ! VGSE()->helpers->user_can_manage_options() ) {
        wp_send_json_error(...);
    }
    
  3. Sink: The function calls save_removed_columns($columns, $post_type), which executes update_option( $this->removed_columns_key, $removed_columns, false ). This updates the site-wide vgse_removed_columns option, affecting all users' view of the spreadsheet.

4. Nonce Acquisition Strategy

The nonce is localized for the spreadsheet editor. Since the vulnerability requires Author-level access, we must obtain the nonce from the editor interface.

  1. Identify Editor Page: The Product Editor page is typically located at /wp-admin/admin.php?page=vgse-view-product.
  2. Access Page: Use the browser_navigate tool to go to this URL as an Author.
  3. Extract Nonce: The plugin localizes settings in the vgse_editor_settings object.
    • JS Variable: window.vgse_editor_settings
    • Nonce Key: nonce
  4. Execution: Use browser_eval("window.vgse_editor_settings?.nonce") to retrieve the token.

5. Exploitation Strategy

Step-by-Step Plan

  1. Preparation: Ensure the environment has WooCommerce and the WP Sheet Editor plugin active. Create an Author user.
  2. Nonce Retrieval:
    • Log in as the Author.
    • Navigate to the Product Sheet: /wp-admin/admin.php?page=vgse-view-product.
    • Extract the nonce using browser_eval.
  3. Malicious Request:
    • Use the http_request tool to send a POST request to admin-ajax.php.
    • Target: Remove the "Regular Price" column (_regular_price) to disrupt store management.
  4. HTTP Request Details:
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=vgse_remove_column&post_type=product&column=_regular_price&nonce=[EXTRACTED_NONCE]
      

6. Test Data Setup

  1. Install: WooCommerce and "Bulk Edit Products for WooCommerce – WP Sheet Editor".
  2. Content: Add at least one test product so the spreadsheet loads data.
  3. User: Create a user with the author role. Ensure they have the edit_products capability (default for WooCommerce Authors).

7. Expected Results

  • Response: The AJAX request should return a JSON success message (e.g., {"success":true,"data":{...}}).
  • Impact: The "Regular Price" column will disappear from the spreadsheet for all users, including administrators.
  • Database: The WordPress option vgse_removed_columns will now contain _regular_price for the product entry.

8. Verification Steps

After the exploit, use WP-CLI to verify the state of the system:

  1. Check Options:
    wp option get vgse_removed_columns --format=json
    
  2. Verify Content: Ensure the output JSON includes "_regular_price" under the product key.
  3. UI Verification: Navigate to the editor as an Administrator and confirm the "Regular Price" column is missing from the table.

9. Alternative Approaches

If vgse_remove_column is patched or behaves differently, attempt to exploit vgse_update_columns_visibility:

  • Action: vgse_update_columns_visibility
  • Body: action=vgse_update_columns_visibility&post_type=product&columns[0][key]=_regular_price&columns[0][status]=disabled&nonce=[NONCE]
  • This endpoint targets the visibility status settings stored in vgse_columns_visibility.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Bulk Edit Products for WooCommerce – WP Sheet Editor plugin (<= 1.8.21) contains a missing authorization vulnerability in its column visibility AJAX handlers. Authenticated attackers with Author-level access can bypass capability checks to globally hide or modify spreadsheet columns by sending unauthorized AJAX requests. This allows lower-privileged users to manipulate site-wide spreadsheet configurations and disrupt store management.

Vulnerable Code

// modules/columns-visibility/columns-visibility.php lines 34-36
add_action( 'wp_ajax_vgse_update_columns_visibility', array( $this, 'update_columns_settings' ) );
add_action( 'wp_ajax_vgse_remove_column', array( $this, 'remove_column' ) );
add_action( 'wp_ajax_vgse_restore_columns', array( $this, 'restore_columns' ) );

---

// modules/columns-visibility/columns-visibility.php lines 218+
public function update_columns_settings() {
    if ( ! empty( $_POST['extra_data'] ) ) {
        // When we render the form in the spreadsheet editor, we send the form data as JSON in extra_data because some servers have low limits for form post fields
        $extra_data = json_decode( html_entity_decode( wp_unslash( $_POST['extra_data'] ) ), true );
        if ( ! is_array( $extra_data ) ) {
            wp_send_json_error( array( 'message' => esc_html__( 'We received invalid column data. Please make sure that your column titles don\'t contain special characters.', 'vg_sheet_editor' ) ) );
        }
        // ... (missing capability check before processing data and updating options)

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.21/modules/columns-visibility/columns-visibility.php /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.22/modules/columns-visibility/columns-visibility.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.21/modules/columns-visibility/columns-visibility.php	2026-01-05 23:38:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.22/modules/columns-visibility/columns-visibility.php	2026-07-09 00:05:20.000000000 +0000
@@ -218,7 +218,8 @@
 		public function update_columns_settings() {
 			if ( ! empty( $_POST['extra_data'] ) ) {
 				// When we render the form in the spreadsheet editor, we send the form data as JSON in extra_data because some servers have low limits for form post fields
-				$extra_data = json_decode( html_entity_decode( wp_unslash( $_POST['extra_data'] ) ), true );
+				$extra_data = json_decode( wp_unslash( $_POST['extra_data'] ), true );
+				
 				if ( ! is_array( $extra_data ) ) {
 					wp_send_json_error( array( 'message' => esc_html__( 'We received invalid column data. Please make sure that your column titles don\'t contain special characters.', 'vg_sheet_editor' ) ) );
 				}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.21/modules/filters/filters.php /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.22/modules/filters/filters.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.21/modules/filters/filters.php	2026-01-17 02:49:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-bulk-edit-products/1.8.22/modules/filters/filters.php	2026-07-09 00:05:20.000000000 +0000
@@ -196,12 +196,12 @@
 				$internal_join = 'OR';
 			}
 
-			$checks          = array();
 			$search_columns  = array( 'post_title', 'post_content', 'post_excerpt' );
 			$phrases         = array_map( 'trim', explode( ';', $raw_keywords ) );
 			$prepared_values = array();
 			$phrase_checks   = array();
 			foreach ( $phrases as $phrase ) {
+				$word_checks_for_phrase = array();
 				$words = explode( ' ', $phrase );
 				if ( empty( $words ) ) {
 					continue;
@@ -210,7 +210,7 @@
 				foreach ( $words as $word ) {
 					$word_checks = array();
 					foreach ( $search_columns as $search_column ) {
-						$word_checks[]   = $wpdb->posts . '.%i ' . $operator . ' %s';
+						$word_checks[] = $wpdb->posts . '.%i ' . $operator . ' %s';
 						$prepared_values = array_merge( $prepared_values, array( $search_column, '%' . $wpdb->esc_like( $word ) . '%' ) );
 					}
 					if ( is_numeric( $phrase ) ) {
@@ -218,14 +218,16 @@
 						$prepared_values[] = 'ID';
 						$prepared_values[] = intval( $phrase );
 					}
-					$phrase_checks[] = '( ' . implode( " $internal_join ", $word_checks ) . ' )';
+					$word_checks_for_phrase[] = '( ' . implode( " $internal_join ", $word_checks ) . ' )';
 				}
-				$all_checks = implode( ' AND ', $phrase_checks );
-				$checks     = apply_filters( 'vg_sheet_editor/filters/search_by_keyword_clauses/keyword_check', $all_checks, $phrase, $clauses, $raw_keywords, $operator, $internal_join );
+				$all_checks = implode( ' AND ', $word_checks_for_phrase );
+
+				$phrase_checks[] = apply_filters( 'vg_sheet_editor/filters/search_by_keyword_clauses/keyword_check', $all_checks, $phrase, $clauses, $raw_keywords, $operator, $internal_join );
 			}
+			$checks = '( ' . implode( $operator === 'NOT LIKE' ? ' AND ' : ' OR ', $phrase_checks ) . ' )';
 			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 			$checks            = $wpdb->prepare( $checks, $prepared_values );
-			$clauses['where'] .= ' AND ( ( ' . $checks . ' ) ) ';
+			$clauses['where'] .= ' AND ( ' . $checks . ' ) ';
 			return apply_filters( 'vg_sheet_editor/filteres/search_by_keyword_clauses', $clauses, $raw_keywords, $operator, $internal_join );
 		}

Exploit Outline

The exploit requires an authenticated user with at least Author-level privileges. First, the attacker accesses the product spreadsheet editor page (vgse-view-product) to extract the localized security nonce from the 'vgse_editor_settings' JavaScript object. Using this nonce, the attacker sends a POST request to 'admin-ajax.php' with the 'action' parameter set to 'vgse_remove_column' or 'vgse_update_columns_visibility'. By providing the desired column key (e.g., '_regular_price'), the attacker triggers the 'save_removed_columns' method, which updates the 'vgse_removed_columns' WordPress option globally, effectively hiding those columns from all users, including administrators.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.