Premmerce Redirect Manager <= 1.0.12 - Missing Authorization
Description
The Premmerce Redirect Manager plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 1.0.12. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.0.12What Changed in the Fix
Changes introduced in v1.0.13
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-32541 Premmerce Redirect Manager <= 1.0.12 ## 1. Vulnerability Summary The **Premmerce Redirect Manager** plugin (up to version 1.0.12) contains a missing authorization vulnerability. While the plugin restricts access to its main administrative interface to u…
Show full research plan
Vulnerability Research Plan: CVE-2026-32541 Premmerce Redirect Manager <= 1.0.12
1. Vulnerability Summary
The Premmerce Redirect Manager plugin (up to version 1.0.12) contains a missing authorization vulnerability. While the plugin restricts access to its main administrative interface to users with the manage_options capability, it registers several admin_post and wp_ajax hooks that fail to perform secondary capability checks within their handler functions. This allows authenticated users with low-level privileges (like Subscribers) to perform actions intended for administrators, specifically deleting redirects.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-post.php(for deletions) or/wp-admin/admin-ajax.php(for information leakage). - Primary Action:
premmerce_delete_redirect(registered viaadmin_posthook). - Secondary Action:
get_posts_by_string(registered viawp_ajaxhook). - Parameters:
action:premmerce_delete_redirectid: The integer ID of the redirect to be deleted._wpnonce: (Potentially required) CSRF token.
- Authentication Level: Authenticated (Subscriber and above).
- Preconditions: At least one redirect must exist in the
wp_premmerce_redirectstable.
3
Summary
The Premmerce Redirect Manager plugin for WordPress is vulnerable to unauthorized access and information disclosure due to missing capability checks and nonce verification on several AJAX and admin-post endpoints. Authenticated attackers, including those with Subscriber-level permissions, can leak the titles of private or password-protected posts and terms, or potentially delete configured redirects.
Vulnerable Code
// src/Admin/Admin.php @ line 125 public function getPostsByString() { if (isset($_POST['type'])) { $objects = $this->model->getPostsByString($_POST); wp_send_json($objects); } } --- // src/RedirectModel.php @ line 166 public function getPostsByString($data) { if (in_array($data['type'], array('product', 'post', 'page'))) { $objects = (new \WP_Query(array( 's' => isset($data['s'])? $data['s'] : '', 'post_type' => $data['type'], 'numberposts' => 10, )))->posts; } else { $objects = get_terms(array( 'hide_empty' => false, 'search' => isset($data['s'])? $data['s'] : '', 'taxonomy' => $data['type'], )); } return $objects; } --- // src/Admin/Admin.php @ line 116 add_action('admin_post_premmerce_delete_redirect', array($this, 'deleteRedirect')); add_action('wp_ajax_get_posts_by_string', array($this, 'getPostsByString'));
Security Fix
@@ -13,6 +13,7 @@ s: params.term, type: 'product', action: 'get_posts_by_string', + _wpnonce: premmerceRedirect.nonce, } }, processResults: function(data) { @@ -39,6 +40,7 @@ s: params.term, type: 'product_cat', action: 'get_posts_by_string', + _wpnonce: premmerceRedirect.nonce, } }, processResults: function(data) { @@ -65,6 +67,7 @@ s: params.term, type: 'category', action: 'get_posts_by_string', + _wpnonce: premmerceRedirect.nonce, } }, processResults: function(data) { @@ -91,6 +94,7 @@ s: params.term, type: 'post', action: 'get_posts_by_string', + _wpnonce: premmerceRedirect.nonce, } }, processResults: function(data) { @@ -117,6 +121,7 @@ s: params.term, type: 'page', action: 'get_posts_by_string', + _wpnonce: premmerceRedirect.nonce, } }, processResults: function(data) { @@ -248,8 +248,20 @@ */ public function getPostsByString() { + if (! current_user_can('manage_options')) { + wp_send_json_error(array( 'message' => __('You do not have permission to perform this action.', 'premmerce-redirect') ), 403); + } + + if (! isset($_POST['_wpnonce']) || ! wp_verify_nonce(wp_unslash($_POST['_wpnonce']), 'premmerce_redirect_search')) { + wp_send_json_error(array( 'message' => __('Security check failed.', 'premmerce-redirect') ), 403); + } + if (isset($_POST['type'])) { - $objects = $this->model->getPostsByString($_POST); + $data = array( + 'type' => sanitize_text_field($_POST['type']), + 's' => isset($_POST['s']) ? sanitize_text_field($_POST['s']) : '', + ); + $objects = $this->model->getPostsByString($data); wp_send_json($objects); } @@ -327,6 +339,9 @@ { wp_enqueue_script('select2', $this->fileManager->locateAsset('admin/js/select2.min.js')); wp_enqueue_script('premmerce-redirect', $this->fileManager->locateAsset('admin/js/premmerce-redirect.js')); + wp_localize_script('premmerce-redirect', 'premmerceRedirect', array( + 'nonce' => wp_create_nonce('premmerce_redirect_search'), + )); wp_enqueue_style('select2', $this->fileManager->locateAsset('admin/css/select2.min.css')); wp_enqueue_style('premmerce-redirect', $this->fileManager->locateAsset('admin/css/premmerce-redirect.css')); @@ -166,18 +166,25 @@ */ public function getPostsByString($data) { - if (in_array($data['type'], array('product', 'post', 'page'))) { + $allowed_post_types = array( 'product', 'post', 'page' ); + $allowed_taxonomies = array( 'product_cat', 'category' ); + + if (in_array($data['type'], $allowed_post_types, true)) { $objects = (new \WP_Query(array( - 's' => isset($data['s'])? $data['s'] : '', - 'post_type' => $data['type'], - 'numberposts' => 10, + 's' => isset($data['s']) ? $data['s'] : '', + 'post_type' => $data['type'], + 'posts_per_page' => 10, + 'has_password' => false, + 'post_status' => 'publish', )))->posts; - } else { + } elseif (in_array($data['type'], $allowed_taxonomies, true)) { $objects = get_terms(array( 'hide_empty' => false, - 'search' => isset($data['s'])? $data['s'] : '', + 'search' => isset($data['s']) ? $data['s'] : '', 'taxonomy' => $data['type'], )); + } else { + $objects = array(); } return $objects;
Exploit Outline
The exploit targets the AJAX endpoint `get_posts_by_string` which is available to all authenticated users. 1. Authentication: Log in as a low-privileged user (Subscriber). 2. Request: Send a POST request to `/wp-admin/admin-ajax.php`. 3. Parameters: - Set `action` to `get_posts_by_string`. - Set `type` to a sensitive post type like `post`, `page`, or `product`. - Set `s` to a search term (empty or specific string). 4. Outcome: The server returns a JSON array of objects containing the IDs and titles of the matching posts. Because the vulnerable version lacks query constraints, this includes titles of posts with statuses like 'draft', 'pending', or 'private', as well as password-protected posts.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.