WPBot – AI ChatBot for Live Support, Lead Generation, AI Services <= 7.7.9 - Unauthenticated SQL Injection
Description
The WPBot – AI ChatBot for Live Support, Lead Generation, AI Services plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 7.7.9 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
What Changed in the Fix
Changes introduced in v7.8.0
Source Code
WordPress.org SVNThis research plan outlines the steps required to exploit an unauthenticated SQL injection vulnerability in the WPBot plugin for WordPress. ### 1. Vulnerability Summary The **WPBot** plugin (versions <= 7.7.9) is vulnerable to an unauthenticated SQL injection. The vulnerability exists in the `wpbo_…
Show full research plan
This research plan outlines the steps required to exploit an unauthenticated SQL injection vulnerability in the WPBot plugin for WordPress.
1. Vulnerability Summary
The WPBot plugin (versions <= 7.7.9) is vulnerable to an unauthenticated SQL injection. The vulnerability exists in the wpbo_search_site function (found in qcld-wpwbot-search.php), specifically when the enable_wp_chatbot_post_content option is enabled. The plugin fails to properly escape the keyword parameter before using it in a SQL fragment within the wpbot_flexible_search_filter function, which is attached to the WordPress posts_search filter.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wpbo_search_site(inferred from function name and standard plugin patterns) - Vulnerable Parameter:
keyword - Authentication: Unauthenticated (the plugin registers
wp_ajax_nopriv_wpbo_search_site) - Precondition: The WordPress option
enable_wp_chatbot_post_contentmust be set to1.
3. Code Flow
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwithaction=wpbo_search_site. - Handler: The function
wpbo_search_site()inqcld-wpwbot-search.phpis executed. - Input Handling:
- The code retrieves
$keywordfrom$_POST['keyword']and passes it throughsanitize_text_field(). sanitize_text_field()strips HTML tags but does not escape SQL metacharacters like single quotes (').
- The code retrieves
- Word Variations: The function `_wpbot_generate
Summary
The WPBot plugin for WordPress is vulnerable to unauthenticated SQL injection through the wpbo_search_site_pagination AJAX action. This occurs due to the improper use of sanitize_text_field() instead of WordPress prepared statements, allowing attackers to inject SQL commands via the 'keyword' and 'type' parameters.
Vulnerable Code
// qcld-wpwbot-search.php line 320 function wpbo_search_site_pagination() { global $wpdb; $keyword = sanitize_text_field( $_POST['keyword'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing $post_type = sanitize_text_field( $_POST['type'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing $page = sanitize_text_field( $_POST['page'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing $enable_post_types = get_option( 'wppt_post_types' ); // ... (logic for search settings) $searchkeyword = qcld_wpbot_modified_keyword( $keyword ); // ... // qcld-wpwbot-search.php line 350 if ( get_option( 'active_advance_query' ) != '1' ) { $sql = 'SELECT * FROM ' . $wpdb->prefix . "posts where post_type in ('" . $post_type . "') and post_status='publish' and ((post_title LIKE '%" . $searchkeyword . "%')) order by ID DESC"; $limit = ' Limit 0, ' . $searchlimit; } else { // advance query building $sql = 'SELECT * FROM ' . $wpdb->prefix . "posts where post_type in ('" . $post_type . "') and post_status='publish' and ((post_title REGEXP '\\b" . $searchkeyword . "\\b') or (post_content REGEXP '\\b" . $searchkeyword . "\\b')) order by ID DESC"; $limit = ' Limit 0, ' . $searchlimit; } $total_results = $wpdb->get_results( $sql ); if ( ! empty( $total_results ) ) { // ... if ( $orderby != 'none' or $orderby != 'rand' ) { $sql .= " order by $orderby $order"; } $limit = ' Limit ' . ( $total_items * $page ) . ", $total_items"; $results = $wpdb->get_results( $sql . $limit ); } else {
Security Fix
@@ -2238,7 +2238,7 @@ var post_type = obj.attr('data-post_type'); var page = obj.attr('data-page'); obj.text('Loading...'); - var data = {'action':'wpbo_search_site_pagination','name':globalwpw.hasNameCookie,'keyword':keyword,'language': globalwpw.settings.obj.language,'type': post_type, 'page': page}; + var data = {'action':'wpbo_search_site_pagination','name':globalwpw.hasNameCookie,'keyword':keyword,'language': globalwpw.settings.obj.language,'type': post_type, 'page': page, 'nonce': qcld_chatbot_obj.nonce}; if($(globalwpw.settings.messageLastChild+' .wp-chatbot-comment-loader').length==0){ $(globalwpw.settings.messageContainer).append(wpwKits.botPreloader()); } @@ -320,9 +320,23 @@ function wpbo_search_site_pagination() { global $wpdb; - $keyword = sanitize_text_field( $_POST['keyword'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing - $post_type = sanitize_text_field( $_POST['type'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing - $page = sanitize_text_field( $_POST['page'] );// phpcs:ignore WordPress.Security.NonceVerification.Missing + // Verify nonce for security + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'wpbot_search_nonce' ) ) { + wp_send_json_error( array( 'message' => 'Security check failed' ) ); + wp_die(); + } + + // Sanitize and validate inputs + $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field( $_POST['keyword'] ) : ''; + $post_type = isset( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : 'post'; + $page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0; + + // Validate post type against allowed types + $allowed_post_types = array( 'post', 'page', 'product' ); + if ( ! in_array( $post_type, $allowed_post_types, true ) ) { + $post_type = 'post'; + } + $enable_post_types = get_option( 'wppt_post_types' ); $load_more = maybe_unserialize( get_option( 'qlcd_wp_chatbot_load_more' ) ); @@ -332,14 +346,17 @@ if ( is_array( $load_more ) ) { $load_more = $load_more[ array_rand( $load_more ) ]; } - $searchlimit = ( get_option( 'wppt_number_of_result' ) == '' ? '5' : get_option( 'wppt_number_of_result' ) ); + $searchlimit = ( get_option( 'wppt_number_of_result' ) == '' ? 5 : absint( get_option( 'wppt_number_of_result' ) ) ); $orderby = ( get_option( 'wppt_result_orderby' ) == '' ? 'none' : get_option( 'wppt_result_orderby' ) ); $order = ( get_option( 'wppt_result_order' ) == '' ? 'ASC' : get_option( 'wppt_result_order' ) ); $thumb = ( get_option( 'wpbot_search_image_size' ) ? get_option( 'wpbot_search_image_size' ) : 'thumbnail' ); // order by setup $new_window = get_option( 'wpbot_search_result_new_window' ); - $total_items = get_option( 'wppt_number_of_result' ); + $total_items = absint( get_option( 'wppt_number_of_result' ) ); + if ( $total_items < 1 ) { + $total_items = 5; + } $searchkeyword = qcld_wpbot_modified_keyword( $keyword ); @@ -347,19 +364,42 @@ $response['status'] = 'fail'; $response['html'] = ''; - // $sql = "SELECT * FROM ". $wpdb->prefix."posts where post_type in ('".$post_type."') and post_status='publish' and ((post_title REGEXP '\\b".$searchkeyword."\\b'))"; + // Use prepared statements to prevent SQL injection if ( get_option( 'active_advance_query' ) != '1' ) { - $sql = 'SELECT * FROM ' . $wpdb->prefix . "posts where post_type in ('" . $post_type . "') and post_status='publish' and ((post_title LIKE '%" . $searchkeyword . "%')) order by ID DESC"; - $limit = ' Limit 0, ' . $searchlimit; + // Simple query - search in post_title only + $sql = $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}posts + WHERE post_type = %s + AND post_status = 'publish' + AND post_title LIKE %s + ORDER BY ID DESC", + $post_type, + '%' . $wpdb->esc_like( $searchkeyword ) . '%' + ); } else { - // advance query building - $sql = 'SELECT * FROM ' . $wpdb->prefix . "posts where post_type in ('" . $post_type . "') and post_status='publish' and ((post_title REGEXP '\\b" . $searchkeyword . "\\b') or (post_content REGEXP '\\b" . $searchkeyword . "\\b')) order by ID DESC"; - $limit = ' Limit 0, ' . $searchlimit; + // Advanced query - search in both post_title and post_content + $sql = $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}posts + WHERE post_type = %s + AND post_status = 'publish' + AND (post_title REGEXP %s OR post_content REGEXP %s) + ORDER BY ID DESC", + $post_type, + '[[:<:]]' . $searchkeyword . '[[:>:]]', + '[[:<:]]' . $searchkeyword . '[[:>:]]' + ); } + $total_results = $wpdb->get_results( $sql );
Exploit Outline
1. Endpoint: Send an unauthenticated POST request to /wp-admin/admin-ajax.php. 2. Action: Set the 'action' parameter to 'wpbo_search_site_pagination'. 3. Parameters: - 'keyword': Provide a keyword (e.g., 'test'). - 'type': Provide a SQL injection payload (e.g., "post') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -"). This parameter is used directly in a SQL query's 'IN' clause. 4. Vulnerability mechanism: The plugin uses sanitize_text_field() on inputs but fails to use $wpdb->prepare(). This allows an attacker to break out of the single-quoted string context in the query and append malicious SQL syntax. 5. Preconditions: No specific options are strictly required for wpbo_search_site_pagination, although the plugin's search features must be active (default behavior).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.