Bulk Edit Products for WooCommerce – WP Sheet Editor <= 1.8.21 - Missing Authorization
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:NTechnical Details
<=1.8.21What Changed in the Fix
Changes introduced in v1.8.22
Source Code
WordPress.org SVN# 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 potentiallyvgse_update_columns_visibility) - Required Authentication: Authenticated, Author-level access (
PR:L). Authors generally have access to the WooCommerce Product spreadsheet if they have theedit_productscapability. - Payload Parameters:
action:vgse_remove_columnpost_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
- Registration: In
modules/columns-visibility/columns-visibility.php, theinit()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' ) ); - Missing Check: In version 1.8.21, the
remove_column()function (andupdate_columns_settings) likely performs a nonce check usingVGSE()->helpers->verify_nonce_from_request()but lacks the secondary capability check seen inrestore_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(...); } - Sink: The function calls
save_removed_columns($columns, $post_type), which executesupdate_option( $this->removed_columns_key, $removed_columns, false ). This updates the site-widevgse_removed_columnsoption, 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.
- Identify Editor Page: The Product Editor page is typically located at
/wp-admin/admin.php?page=vgse-view-product. - Access Page: Use the
browser_navigatetool to go to this URL as an Author. - Extract Nonce: The plugin localizes settings in the
vgse_editor_settingsobject.- JS Variable:
window.vgse_editor_settings - Nonce Key:
nonce
- JS Variable:
- Execution: Use
browser_eval("window.vgse_editor_settings?.nonce")to retrieve the token.
5. Exploitation Strategy
Step-by-Step Plan
- Preparation: Ensure the environment has WooCommerce and the WP Sheet Editor plugin active. Create an Author user.
- 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.
- Malicious Request:
- Use the
http_requesttool to send a POST request toadmin-ajax.php. - Target: Remove the "Regular Price" column (
_regular_price) to disrupt store management.
- Use the
- 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]
- URL:
6. Test Data Setup
- Install: WooCommerce and "Bulk Edit Products for WooCommerce – WP Sheet Editor".
- Content: Add at least one test product so the spreadsheet loads data.
- User: Create a user with the
authorrole. Ensure they have theedit_productscapability (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_columnswill now contain_regular_pricefor theproductentry.
8. Verification Steps
After the exploit, use WP-CLI to verify the state of the system:
- Check Options:
wp option get vgse_removed_columns --format=json - Verify Content: Ensure the output JSON includes
"_regular_price"under theproductkey. - 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.
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
@@ -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' ) ) ); } @@ -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.