Schedule Post Changes With PublishPress Future: Unpublish, Delete, Change Status, Trash, Change Categories <= 4.9.2 - Missing Authorization to Authenticated (Contributor+) Authors' Emails Exposure
Description
The Schedule Post Changes With PublishPress Future: Unpublish, Delete, Change Status, Trash, Change Categories plugin for WordPress is vulnerable to unauthorized access of data due to a missing capability check on the getAuthors function in all versions up to, and including, 4.9.2. This makes it possible for authenticated attackers, with Contributor-level access and above, to retrieve emails for all users with edit_posts capability.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=4.9.2Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-13741 ## 1. Vulnerability Summary **CVE-2025-13741** is a missing authorization vulnerability in the **PublishPress Future** (formerly Post Expirator) plugin for WordPress. The vulnerability exists in the `getAuthors` function (likely within an AJAX or REST ha…
Show full research plan
Exploitation Research Plan: CVE-2025-13741
1. Vulnerability Summary
CVE-2025-13741 is a missing authorization vulnerability in the PublishPress Future (formerly Post Expirator) plugin for WordPress. The vulnerability exists in the getAuthors function (likely within an AJAX or REST handler), which fails to implement a capability check beyond the basic authenticated user check. This allows any user with the edit_posts capability (Contributor role and above) to execute this function and retrieve the email addresses of all users on the site who have the edit_posts capability, leading to unauthorized data exposure.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php(inferred as the most common entry point for this plugin's author-fetching features). - Action:
postexpirator_get_authors(inferred from plugin slug and function name). - Authentication: Contributor-level session (
edit_postscapability required). - Payload Parameter:
qorterm(often used for autocomplete search strings). - Preconditions: The attacker must be logged in as a Contributor or higher.
3. Code Flow (Inferred)
- Entry Point: A request is sent to
admin-ajax.phpwithaction=postexpirator_get_authors. - Hook Registration: The plugin registers the action using
add_action('wp_ajax_postexpirator_get_authors', [...]). - Handler Function: The registered callback calls
getAuthors(). - Data Retrieval:
getAuthors()usesget_users()to fetch users with theedit_postscapability or specific roles. - Information Leak: The function fails to filter out sensitive fields (like
user_email) or fails to check if the current user haslist_usersormanage_optionspermissions before returning the full user objects or an array containing emails. - Response: A JSON array containing user details, including emails, is returned to the Contributor.
4. Nonce Acquisition Strategy
The plugin likely localizes a nonce for its AJAX operations on the post editing screen, which Contributors can access.
- Shortcode/Context: The "PublishPress Future" meta box appears on the Post Edit screen for any post type the plugin is enabled for (usually 'post' and 'page').
- Access: Log in as a Contributor and navigate to the "Add New Post" page.
- Extraction: Use
browser_evalto find the nonce in the localized JS object.- Target Variable:
window.publishpressFutureConfigorwindow.postExpirator. - Command:
browser_eval("window.publishpressFutureConfig?.nonce || window.postExpirator?.nonce") - Manual Check: If the variable name differs, search the page source for "nonce" within
<script>tags usingbrowser_source.
- Target Variable:
5. Exploitation Strategy
The exploit involves sending an authenticated AJAX request to the vulnerable handler and capturing the JSON response containing emails.
- Log in: Authenticate as a Contributor.
- Capture Nonce: Navigate to
wp-admin/post-new.phpand extract the nonce using the strategy in Section 4. - Execute Request: Send a POST request to
admin-ajax.php.
HTTP Request (via http_request tool):
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Cookie: [Contributor Cookies]
action=postexpirator_get_authors&security=[NONCE_VALUE]&term=@
(Note: The parameter security or _wpnonce and the action name should be confirmed by inspecting the localized script found in Section 4.)
- Capture Response: Verify if the JSON response contains an array of objects with the
emailoruser_emailkey.
6. Test Data Setup
- Users:
- Create an Administrator (
admin_user,admin@victim.com). - Create an Editor (
editor_user,editor@victim.com). - Create a Contributor (
attacker_user,attacker@victim.com).
- Create an Administrator (
- Plugin Configuration:
- Ensure "PublishPress Future" is active.
- Ensure the plugin is enabled for the 'post' post type (default).
7. Expected Results
- Success: The response is a JSON status
200 OKcontaining a list of users. Each user object includes the field"user_email": "admin@victim.com"and"user_email": "editor@victim.com". - Failure: The response returns a
403 Forbiddenor a0(WordPress default for failed AJAX), or the user objects returned are stripped of the email field.
8. Verification Steps
- Identify Exposed Data: Parse the JSON response from the exploit.
- Compare with DB: Run a WP-CLI command to verify the emails match the database.
wp user list --fields=user_login,user_email --role=administrator,editor - Check Permission Logic: Attempt the same request without a valid session to confirm the vulnerability requires authentication (matching the "Contributor+" report).
9. Alternative Approaches
- REST API: If the AJAX handler is not found, check for REST API routes registered by the plugin:
GET /wp-json/publishpress-future/v1/authors- This can be found by grepping for
register_rest_routein the plugin directory.
- Action Guessing: If
postexpirator_get_authorsfails, grep the plugin code for the stringgetAuthorsto find the exactwp_ajax_registration:grep -r "getAuthors" /var/www/html/wp-content/plugins/post-expirator/ - Parameter Variation: If the
termparameter doesn't return all users, try omitting it or using a wildcard like*.
Summary
The PublishPress Future plugin for WordPress fails to perform an adequate authorization check within its user-fetching logic. This allows authenticated users with Contributor-level permissions or higher to execute the getAuthors function via an AJAX request and retrieve sensitive data, including the email addresses of all users on the site who possess the 'edit_posts' capability.
Vulnerable Code
// Inferred registration of the AJAX action in the plugin's initialization add_action('wp_ajax_postexpirator_get_authors', array($this, 'getAuthors')); // Inferred handler function lacking specific capability checks for sensitive data access public function getAuthors() { // Often, basic nonce validation is present, but not a capability check like current_user_can('list_users') $term = isset($_GET['term']) ? sanitize_text_field($_GET['term']) : ''; $query_args = array( 'search' => '*' . $term . '*', 'search_columns' => array('user_login', 'display_name'), 'fields' => array('ID', 'display_name', 'user_email'), // Exposure of user_email ); $users = get_users($query_args); wp_send_json_success($users); }
Security Fix
@@ -10,6 +10,11 @@ public function getAuthors() { + if (! current_user_can('manage_options') && ! current_user_can('list_users')) { + wp_send_json_error('Unauthorized', 403); + return; + } + check_ajax_referer('postexpirator_nonce', 'security'); $term = isset($_GET['term']) ? sanitize_text_field($_GET['term']) : ''; $users = get_users(array( 'search' => '*' . $term . '*', 'fields' => array('ID', 'display_name', 'user_email'), ));
Exploit Outline
The vulnerability is exploited by an authenticated attacker with Contributor-level privileges (which includes the 'edit_posts' capability). 1. The attacker logs into the WordPress dashboard. 2. They navigate to the post editor (wp-admin/post-new.php) to extract a security nonce from the localized script variables (e.g., window.publishpressFutureConfig.nonce). 3. The attacker then sends a POST or GET request to '/wp-admin/admin-ajax.php' using the action 'postexpirator_get_authors' and providing the captured nonce. 4. Because the server-side function fails to check for administrative or user-listing capabilities, it returns a JSON object containing the full list of editors and administrators, including their registered email addresses.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.