CVE-2025-13741

Schedule Post Changes With PublishPress Future: Unpublish, Delete, Change Status, Trash, Change Categories <= 4.9.2 - Missing Authorization to Authenticated (Contributor+) Authors' Emails Exposure

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.9.3
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=4.9.2
PublishedDecember 15, 2025
Last updatedDecember 16, 2025
Affected pluginpost-expirator

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_posts capability required).
  • Payload Parameter: q or term (often used for autocomplete search strings).
  • Preconditions: The attacker must be logged in as a Contributor or higher.

3. Code Flow (Inferred)

  1. Entry Point: A request is sent to admin-ajax.php with action=postexpirator_get_authors.
  2. Hook Registration: The plugin registers the action using add_action('wp_ajax_postexpirator_get_authors', [...]).
  3. Handler Function: The registered callback calls getAuthors().
  4. Data Retrieval: getAuthors() uses get_users() to fetch users with the edit_posts capability or specific roles.
  5. Information Leak: The function fails to filter out sensitive fields (like user_email) or fails to check if the current user has list_users or manage_options permissions before returning the full user objects or an array containing emails.
  6. 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.

  1. 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').
  2. Access: Log in as a Contributor and navigate to the "Add New Post" page.
  3. Extraction: Use browser_eval to find the nonce in the localized JS object.
    • Target Variable: window.publishpressFutureConfig or window.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 using browser_source.

5. Exploitation Strategy

The exploit involves sending an authenticated AJAX request to the vulnerable handler and capturing the JSON response containing emails.

  1. Log in: Authenticate as a Contributor.
  2. Capture Nonce: Navigate to wp-admin/post-new.php and extract the nonce using the strategy in Section 4.
  3. 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.)

  1. Capture Response: Verify if the JSON response contains an array of objects with the email or user_email key.

6. Test Data Setup

  1. 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).
  2. 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 OK containing 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 Forbidden or a 0 (WordPress default for failed AJAX), or the user objects returned are stripped of the email field.

8. Verification Steps

  1. Identify Exposed Data: Parse the JSON response from the exploit.
  2. 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
    
  3. 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_route in the plugin directory.
  • Action Guessing: If postexpirator_get_authors fails, grep the plugin code for the string getAuthors to find the exact wp_ajax_ registration:
    grep -r "getAuthors" /var/www/html/wp-content/plugins/post-expirator/
    
  • Parameter Variation: If the term parameter doesn't return all users, try omitting it or using a wildcard like *.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/src/PostExpirator/Controllers/AuthorsController.php
+++ b/src/PostExpirator/Controllers/AuthorsController.php
@@ -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.