CVE-2026-42738

Smart Online Order for Clover <= 1.6.0 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
1.6.1
Patched in
6d
Time to patch

Description

The Smart Online Order for Clover plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.6.0 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.6.0
PublishedMay 27, 2026
Last updatedJune 1, 2026
Affected pluginclover-online-orders

What Changed in the Fix

Changes introduced in v1.6.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-42738 ## 1. Vulnerability Summary The **Smart Online Order for Clover** plugin (versions <= 1.6.0) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin registers unauthenticated REST API endpoints…

Show full research plan

Exploitation Research Plan - CVE-2026-42738

1. Vulnerability Summary

The Smart Online Order for Clover plugin (versions <= 1.6.0) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin registers unauthenticated REST API endpoints that accept user-supplied data (such as customer names during checkout or item data during synchronization) and subsequently displays this data in the WordPress admin dashboard without proper sanitization or output escaping.

The primary sinks are found in the WP_List_Table implementations used to display orders and products, where data is output using stripslashes() instead of escaping functions like esc_html().

2. Attack Vector Analysis

  • Target Endpoint: WordPress REST API.
  • Namespace: moo-clover/v1 (defined in includes/moo-OnlineOrders-Restapi.php).
  • Vulnerable Routes (Inferred):
    • POST /wp-json/moo-clover/v1/checkout/place_order (handled by CheckoutRoutes).
    • POST /wp-json/moo-clover/v1/sync/items (handled by SyncRoutes).
  • Vulnerable Parameter: customer_name, first_name, last_name, or soo_name.
  • Authentication: None. The Moo_OnlineOrders_Restapi::register_routes function uses 'permission_callback' => '__return_true' for these routes.

3. Code Flow

  1. Input: An unauthenticated attacker sends a POST request to /wp-json/moo-clover/v1/checkout/place_order.
  2. Processing: The CheckoutRoutes class (instantiated in Moo_OnlineOrders_Restapi::__construct) processes the request and stores the order details (including the malicious customer_name) via the Moo_OnlineOrders_Model or by sending it to the Smart Online Order API.
  3. Storage: The payload is stored in the database (either locally in wp_moo_orders or cached via the SooApi).
  4. Admin Access: An administrator navigates to the "Orders" page in the dashboard: wp-admin/admin.php?page=moo_orders.
  5. Data Retrieval: Orders_List_Moo::prepare_items() calls self::get_items(), which fetches the malicious data via $api->getOrdersByPage() (see admin/includes/class-moo-orders-list.php).
  6. Output Sink: The Orders_List_Moo::column_default() method processes the customer_name column:
    case 'customer_name':
        if (isset($item[$column_name]))
            return stripslashes((string)$item[$column_name]); // XSS SINK
    
  7. Execution: The unescaped script executes in the administrator's browser.

4. Nonce Acquisition Strategy

The REST API endpoints for this plugin explicitly bypass authentication and nonce checks by returning true in the permission_callback.

  • File: includes/moo-OnlineOrders-Restapi.php
  • Logic:
    register_rest_route( $this->namespace, '/cart', array(
        array(
            'methods'   => 'POST',
            'callback'  => array( $this, 'addItemToCart' ),
            'permission_callback' => '__return_true' // No nonce required
        )
    ) );
    

As such, no nonce is required for the initial injection.

5. Exploitation Strategy

The goal is to inject a script into the "Customer Name" field via the checkout REST API.

Step 1: Discover the exact route

Since the full content of CheckoutRoutes.php is not provided, we first verify the registered routes.

  1. Use http_request to GET http://localhost:8080/wp-json/moo-clover/v1.
  2. Locate the route related to "checkout" or "place order".

Step 2: Inject the Payload

Send a POST request to the identified checkout endpoint.

  • URL: http://localhost:8080/wp-json/moo-clover/v1/checkout/place_order (inferred)
  • Method: POST
  • Headers: Content-Type: application/json
  • Body:
    {
        "first_name": "John",
        "last_name": "Doe<script>alert(document.domain)</script>",
        "email": "attacker@example.com",
        "phone": "1234567890",
        "order_type": "pickup",
        "items": []
    }
    

Step 3: Trigger the XSS

  1. Log into the WordPress admin panel as an administrator.
  2. Navigate to http://localhost:8080/wp-admin/admin.php?page=moo_orders.
  3. The alert box should trigger when the "Orders" table renders.

6. Test Data Setup

  1. Plugin Configuration: The plugin must be active. Some features may require an "API Key" to be set in Clover Orders > Settings. If the REST API checks for a connected store, use wp option update moo_settings '{"api_key":"mock_key","merchant_id":"mock_id"}' --format=json.
  2. Page Setup: No shortcodes are strictly necessary for the REST API attack, but visiting the homepage ensures the REST API namespace is initialized.

7. Expected Results

  • The REST API should return a 200 OK or 201 Created response.
  • Upon visiting the admin Orders page, the HTML source for the order row should contain:
    <td>John Doe<script>alert(document.domain)</script></td>
  • A browser alert showing the domain name should appear.

8. Verification Steps

After sending the HTTP request, verify the storage using WP-CLI:

# If stored in local orders table
wp db query "SELECT customer_name FROM wp_moo_orders ORDER BY id DESC LIMIT 1;"

# If synced to items table (for SyncRoutes variant)
wp db query "SELECT soo_name FROM wp_moo_item WHERE soo_name LIKE '%<script>%';"

9. Alternative Approaches

If place_order is not the correct endpoint, target the synchronization mechanism:

  • Endpoint: POST /wp-json/moo-clover/v1/sync/items
  • Payload: Set the name or soo_name of an item to the XSS payload.
  • Trigger: View the items list at wp-admin/admin.php?page=moo_items.
  • Code Sink: Products_List_Moo::column_soo_name() which uses stripslashes((string)$itemName) (see admin/includes/class-moo-products-list.php).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Smart Online Order for Clover plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting due to insufficient output escaping in the administrative dashboard and lack of authentication on REST API endpoints. An attacker can inject malicious scripts via checkout or synchronization API calls, which then execute in the browser of an administrator viewing order or product lists.

Vulnerable Code

// admin/includes/class-moo-orders-list.php:121
public function column_default( $item, $column_name ) {
    switch ( $column_name ) {
        case 'order_number':
        case 'customer_name':
        case 'order_type_label':
        case 'status':
        case 'amount':
        case 'created_at_hf':
        case 'source':
            if (isset($item[$column_name]))
                return stripslashes((string)$item[$column_name]);
            return '';

---

// admin/includes/class-moo-products-list.php:231
function column_soo_name( $item ) {
    // ...
    if(!empty($item[ "soo_name" ])){
        $itemName = $item['soo_name'];
    } else {
        if(!empty($item[ "alternate_name" ])){
            $itemName = $item['name'] . " (alternate name : ".$item[ "alternate_name" ].")";
        } else {
            $itemName = $item['name'];
        }
    }

    $title = '<div class="mooItemNameSection" id="item-name-section-for-'.$item['uuid'].'">';
    $title .= '<div class="moo-item-name"><strong>' . stripslashes((string)$itemName) . '</strong></div><img onclick="moo_editItemName(event,\''.$item['uuid'].'\')" style="margin-left: 10px;cursor: pointer" src="'.$this->editIcon.'" alt="placeholder" sizes="(max-width: 150px) 100vw, 150px" id="moo-edit-item-name-".esc_attr($item['uuid'])."'>";

---

// includes/moo-OnlineOrders-Restapi.php:185
register_rest_route( $this->namespace, '/cart', array(
    array(
        'methods'   => 'POST',
        'callback'  => array( $this, 'addItemToCart' ),
        'permission_callback' => '__return_true'
    )
) );

Security Fix

--- a/admin/includes/class-moo-orders-list.php
+++ b/admin/includes/class-moo-orders-list.php
@@ -131,7 +131,7 @@
             case 'created_at_hf':
             case 'source':
                 if (isset($item[$column_name]))
-                    return stripslashes((string)$item[$column_name]);
+                    return esc_html(stripslashes((string)$item[$column_name]));
                 return '';
             default:
                 return print_r( $item, true );
--- a/admin/includes/class-moo-products-list.php
+++ b/admin/includes/class-moo-products-list.php
@@ -253,7 +253,7 @@
         }
 
         $title = '<div class="mooItemNameSection" id="item-name-section-for-'.$item['uuid'].'">';
-        $title .= '<div class="moo-item-name"><strong>' . stripslashes((string)$itemName) . '</strong></div><img onclick="moo_editItemName(event,\''.$item['uuid'].'\')" style="margin-left: 10px;cursor: pointer" src="'.$this->editIcon.'" alt="placeholder" sizes="(max-width: 150px) 100vw, 150px" id="moo-edit-item-name-".esc_attr($item['uuid'])."'>";
+        $title .= '<div class="moo-item-name"><strong>' . esc_html(stripslashes((string)$itemName)) . '</strong></div><img onclick="moo_editItemName(event,\''.$item['uuid'].'\')" style="margin-left: 10px;cursor: pointer" src="'.$this->editIcon.'" alt="placeholder" sizes="(max-width: 150px) 100vw, 150px" id="moo-edit-item-name-".esc_attr($item['uuid'])."'>";
         $title .= '</div>';

Exploit Outline

The exploit targets the plugin's unauthenticated REST API endpoints, specifically those under the `moo-clover/v1` namespace. An attacker sends a POST request to a route such as `/wp-json/moo-clover/v1/checkout/place_order` (or similar endpoints in `CheckoutRoutes` or `SyncRoutes`) without any authentication or nonces. The payload includes a malicious script within parameters like `last_name`, `customer_name`, or `first_name`. Because the REST API returns `true` for its `permission_callback`, the request is processed and the payload is stored. When an administrator later logs into the WordPress dashboard and views the Orders page (`wp-admin/admin.php?page=moo_orders`) or Products page, the plugin retrieves the stored data and outputs it using `stripslashes()` without `esc_html()`, causing the script to execute in the administrator's browser session.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.