CVE-2025-12980

Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX <= 5.0.3 - Missing Authorization to Unauthenticated Sensitive Information Exposure

highMissing Authorization
7.5
CVSS Score
7.5
CVSS Score
high
Severity
5.0.4
Patched in
1d
Time to patch

Description

The Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX plugin for WordPress is vulnerable to unauthorized access of data due to a missing capability check on the '/ultp/v2/get_dynamic_content/' REST API endpoint in all versions up to, and including, 5.0.3. This makes it possible for unauthenticated attackers to retrieve sensitive user metadata, including password hashes.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.0.3
PublishedDecember 20, 2025
Last updatedDecember 21, 2025
Affected pluginultimate-post

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to investigate and exploit **CVE-2025-12980**, a missing authorization vulnerability in the **PostX (ultimate-post)** plugin. This vulnerability allows unauthenticated attackers to access sensitive user data, including password hashes, via a specific REST API en…

Show full research plan

This research plan outlines the steps to investigate and exploit CVE-2025-12980, a missing authorization vulnerability in the PostX (ultimate-post) plugin. This vulnerability allows unauthenticated attackers to access sensitive user data, including password hashes, via a specific REST API endpoint.


1. Vulnerability Summary

  • Vulnerability: Missing Authorization to Unauthenticated Sensitive Information Exposure.
  • Affected Plugin: Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX (slug: ultimate-post).
  • Vulnerable Versions: <= 5.0.3.
  • Vulnerable Endpoint: /wp-json/ultp/v2/get_dynamic_content/.
  • Root Cause: The REST API route ultp/v2/get_dynamic_content/ lacks a proper permission_callback (likely set to __return_true or missing entirely), allowing unauthenticated users to trigger functions that retrieve user data without filtering sensitive fields like user_pass.

2. Attack Vector Analysis

  • Endpoint: /wp-json/ultp/v2/get_dynamic_content/
  • HTTP Method: POST (typical for Gutenberg dynamic fetching).
  • Parameters (Inferred):
    • content_type: Likely specifies what to fetch (e.g., 'user', 'author', or 'post').
    • id or object_id: The ID of the user/post to fetch.
    • attributes or settings: Configuration for which fields to return.
  • Authentication: None required (Unauthenticated).
  • Preconditions: The REST API must be enabled (default in WordPress).

3. Code Flow (Inferred)

  1. Route Registration: The plugin registers the route using register_rest_route in the rest_api_init hook.
    • File: Likely includes/classes/class-ultp-rest-api.php or similar.
    • Code: register_rest_route( 'ultp/v2', '/get_dynamic_content/', ... ).
  2. Missing Check: The permission_callback for this route is either absent or returns true for all users.
  3. Data Retrieval: The callback function (likely get_dynamic_content) receives the POST parameters. It uses these to call functions like get_userdata() or get_user_meta().
  4. Information Leak: The function returns the resulting object directly in the REST response without sanitizing the user_pass field or restricted user_meta keys.

4. Nonce Acquisition Strategy

While REST API routes with permission_callback => '__return_true' often don't strictly require the wp_rest nonce for GET requests, POST requests in WordPress usually require a nonce if the user is authenticated, or the plugin might check a custom nonce for consistency.

  1. Identify Shortcode: PostX uses Gutenberg blocks. Create a page with a PostX block (e.g., a "Post Grid" or "Author" block).
  2. Create Test Page:
    wp post create --post_type=page --post_title="PostX Test" --post_status=publish --post_content='<!-- wp:ultimate-post/post-grid /-->'
    
  3. Navigate and Extract:
    Use the browser_navigate tool to the new page. Use browser_eval to find localized data.
    • Search for: ultp_ajax_obj, postx_obj, or ultp_settings.
    • JavaScript: window.ultp_ajax_obj?.nonce or window.postx_localize?.rest_nonce.
  4. Manual Check: If no nonce is found, the endpoint might be entirely unprotected.

5. Exploitation Strategy

Step 1: Discover Parameters
Since the exact parameter structure is inferred, first attempt to trigger an error or a successful response with a minimal payload to the endpoint.

Step 2: Craft Payload
Attempt to retrieve user ID 1 (typically the admin).

  • Request URL: http://localhost:8080/wp-json/ultp/v2/get_dynamic_content/
  • Headers: Content-Type: application/json
  • Payload (Inferred Candidate A):
    {
        "content_type": "user",
        "object_id": 1
    }
    
  • Payload (Inferred Candidate B - based on PostX "Dynamic Data" feature):
    {
        "source": "user",
        "id": 1,
        "field": "all"
    }
    

Step 3: Execute HTTP Request
Use the http_request tool to send the POST request.

6. Test Data Setup

  1. Ensure PostX is active: wp plugin activate ultimate-post.
  2. Create a Target User: Ensure there is a user with a known ID.
    wp user create victim victim@example.com --user_pass=Password123! --role=administrator
    
  3. Identify User ID:
    wp user list --field=ID --user_login=victim
    

7. Expected Results

  • Success: A 200 OK response with a JSON object.
  • Sensitive Data: The JSON body should contain a key like user_pass or data containing a string starting with $P$ or $wpb$ (the WordPress password hash). It may also expose user_email and user_activation_key.

8. Verification Steps

After receiving the REST response:

  1. Compare Hash: Use WP-CLI to get the actual hash and verify it matches the leaked one.
    wp db query "SELECT user_pass FROM wp_users WHERE ID = 1"
    
  2. Check Meta: If metadata was leaked, verify it against:
    wp user meta list 1
    

9. Alternative Approaches

If the get_dynamic_content endpoint requires specific "Block Settings" to be passed:

  1. Examine Source: Look for render_dynamic_content or get_dynamic_content in the plugin folder (/wp-content/plugins/ultimate-post/) to see how it parses $request->get_params().
  2. Interception: If a page with a PostX block is loaded in the browser, check the "Network" tab in Playwright/Chrome to see if any requests are automatically made to /ultp/v2/get_dynamic_content/ and copy that request's structure.
  3. Varying Content Types: Try content_type values like author, current_user, or user_meta.
Research Findings
Static analysis — not yet PoC-verified

Summary

The PostX plugin for WordPress fails to implement authorization checks on its /ultp/v2/get_dynamic_content/ REST API endpoint. Unauthenticated attackers can exploit this to retrieve sensitive user data, including WordPress password hashes, by querying for specific user IDs through the dynamic content fetcher.

Vulnerable Code

// File: includes/classes/class-ultp-rest-api.php (inferred route registration)
register_rest_route( 'ultp/v2', '/get_dynamic_content/', array(
    'methods'             => 'POST',
    'callback'            => array( $this, 'get_dynamic_content' ),
    'permission_callback' => '__return_true',
) );

---

// File: includes/classes/class-ultp-rest-api.php (inferred callback logic)
public function get_dynamic_content( $request ) {
    $params = $request->get_params();
    $id     = isset($params['id']) ? $params['id'] : 0;
    $type   = isset($params['content_type']) ? $params['content_type'] : '';
    
    if ($type === 'user') {
        return get_userdata($id); // Returns the full WP_User object including user_pass
    }
    // ... other logic
}

Security Fix

--- a/includes/classes/class-ultp-rest-api.php
+++ b/includes/classes/class-ultp-rest-api.php
@@ -25,7 +25,9 @@
         register_rest_route( 'ultp/v2', '/get_dynamic_content/', array(
             'methods'             => 'POST',
             'callback'            => array( $this, 'get_dynamic_content' ),
-            'permission_callback' => '__return_true',
+            'permission_callback' => function () {
+                return current_user_can( 'edit_posts' );
+            },
         ) );

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker targets the WordPress REST API endpoint at /wp-json/ultp/v2/get_dynamic_content/. The attacker sends a POST request with a JSON payload containing parameters such as 'content_type' (set to 'user') and 'id' or 'object_id' (set to the target user's ID, e.g., 1 for the site administrator). Because the endpoint lacks a permission check and fails to sanitize the output, the response body will contain the full WordPress user object, which includes sensitive information like the 'user_pass' hash, 'user_email', and account metadata.

Check if your site is affected.

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