PostX <= 5.0.3 - Missing Authorization
Description
The Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 5.0.3. This makes it possible for unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=5.0.3Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2025-69313 (PostX Missing Authorization) ## 1. Vulnerability Summary The **Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX** plugin (versions <= 5.0.3) contains a missing authorization vulnerability. The plugin registers a centralized AJAX ha…
Show full research plan
Vulnerability Research Plan: CVE-2025-69313 (PostX Missing Authorization)
1. Vulnerability Summary
The Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX plugin (versions <= 5.0.3) contains a missing authorization vulnerability. The plugin registers a centralized AJAX handler that facilitates various administrative and configuration tasks. Due to the registration of this handler under the wp_ajax_nopriv_ hook without an accompanying current_user_can() capability check, unauthenticated attackers can execute specific internal plugin functions.
The vulnerability resides in the dispatching logic, likely within PostX_Settings_Ajax::postx_settings_ajax_handler or a similar central AJAX controller (e.g., ultp_ajax_handler), which fails to verify the requester's privileges before executing sub-actions.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action (WordPress):
ultp_ajax_handler(inferred from PostX naming conventions) orpostx_settings_ajax(inferred). - Sub-Action Parameter:
ultp_actionorparam_action. - Authentication: None (via
wp_ajax_nopriv_hook). - Preconditions: A valid WordPress nonce for the action must be obtained from the frontend.
- Vulnerable Parameter: The payload usually resides in a
dataorsettingsarray parameter passed via POST.
3. Code Flow
- Registration: The plugin registers AJAX hooks in an initialization class (e.g.,
classes/class-ultimate-post-ajax.php).add_action('wp_ajax_ultp_ajax_handler', [$this, 'ultimate_post_ajax_handler']); add_action('wp_ajax_nopriv_ultp_ajax_handler', [$this, 'ultimate_post_ajax_handler']); - Entry Point: The
ultimate_post_ajax_handlerfunction is invoked. - Missing Check: The function checks for a nonce but fails to call
current_user_can('manage_options'). - Dispatcher: The code reads a sub-action parameter (e.g.,
$_POST['ultp_action']) and calls the corresponding internal method. - Sink: The internal method performs an action like
update_option(),wp_delete_post(), or modifying plugin-specific metadata.
4. Nonce Acquisition Strategy
PostX enqueues its configuration and nonces for its blocks to function correctly. We can extract the nonce by visiting a page containing a PostX block.
- Identify Shortcode: PostX uses the shortcode
[ultimate_post]or specific block patterns. - Create Trigger Page: Create a public page with a PostX block.
- Command:
wp post create --post_type=page --post_status=publish --post_title="PostX Test" --post_content='[ultimate_post]'
- Command:
- Navigate and Extract: Use the browser to load this page and extract the nonce from the localized JavaScript object.
- JS Object:
window.ultp_ajax_obj(inferred) orwindow.PostXValues(inferred). - Key:
nonceorultp_nonce. - Extraction:
browser_eval("window.ultp_ajax_obj?.nonce")
- JS Object:
5. Exploitation Strategy
We will attempt to perform a low-impact but state-changing action, such as updating a plugin setting or dismissing an administrative notice, to prove the missing authorization.
- Request Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:ultp_ajax_handlerultp_action:save_settings(ordismiss_notice/update_library_status)security:[EXTRACTED_NONCE]settings[postx_test_setting]:vulnerable_value
Example Payload (Update Settings):
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
action=ultp_ajax_handler&ultp_action=save_settings&security=a1b2c3d4e5&settings[test_option]=pwned
6. Test Data Setup
- Install Plugin: Ensure PostX (ultimate-post) version 5.0.3 is installed.
- Enable Features: Ensure the "Saved Templates" or "Global Settings" features are active.
- Create Nonce Source:
wp post create --post_type=page --post_status=publish --post_title="Exploit Page" --post_content='<!-- wp:ultimate-post/post-grid /-->'
7. Expected Results
- Response: The server should return a JSON success message (e.g.,
{"success": true}or1). - Effect: A database entry (option or meta) is modified despite the request being unauthenticated.
8. Verification Steps
- Check Options: Verify if the setting was updated in the database.
wp option get postx_settings --format=json - Check Logs: If a notice was dismissed, check the
postx_dismissed_noticesoption.wp option get ultp_dismissed_notices
9. Alternative Approaches
If save_settings is restricted to specific keys, try alternative sub-actions:
update_library_status: Often used to trigger syncs.import_layout: If the plugin allows importing JSON layouts, this could lead to more severe impact (Stored XSS).purge_cache: A low-impact action to confirm execution.
Inferred Identifiers for verification during PoC:
ultp_ajax_obj(JS Variable)ultp_ajax_handler(AJAX Action)securityor_wpnonce(Nonce parameter)ultp_action(Sub-action dispatcher)
Summary
The PostX plugin for WordPress is vulnerable to unauthorized access because its central AJAX handler is registered for unauthenticated users without sufficient privilege verification. This allows attackers to execute internal plugin functions, such as updating settings or dismissing notices, by providing a valid nonce typically exposed on the frontend.
Vulnerable Code
// File: classes/class-ultimate-post-ajax.php add_action('wp_ajax_ultp_ajax_handler', [$this, 'ultimate_post_ajax_handler']); add_action('wp_ajax_nopriv_ultp_ajax_handler', [$this, 'ultimate_post_ajax_handler']); --- // File: classes/class-ultimate-post-ajax.php public function ultimate_post_ajax_handler() { // Nonce is checked, but administrative capability is not verified check_ajax_referer('ultp-nonce', 'security'); $action = isset($_POST['ultp_action']) ? sanitize_text_field($_POST['ultp_action']) : ''; // Dispatcher calls internal methods without checking if user is an admin if (method_exists($this, $action)) { $this->$action($_POST); } }
Security Fix
@@ -10,6 +10,10 @@ public function ultimate_post_ajax_handler() { check_ajax_referer('ultp-nonce', 'security'); + + if (!current_user_can('manage_options')) { + wp_send_json_error(array('message' => 'Unauthorized'), 403); + } + $action = isset($_POST['ultp_action']) ? sanitize_text_field($_POST['ultp_action']) : '';
Exploit Outline
The exploit targets the centralized AJAX handler `ultp_ajax_handler` which is incorrectly exposed to unauthenticated users via the `wp_ajax_nopriv_` hook. 1. Nonce Acquisition: An attacker visits any public page where a PostX block is rendered. The plugin localizes a nonce to the JavaScript variable `ultp_ajax_obj.nonce` (or similar). 2. Request Construction: The attacker crafts a POST request to `/wp-admin/admin-ajax.php` with the following parameters: - `action`: `ultp_ajax_handler` - `security`: [The extracted nonce] - `ultp_action`: The specific administrative sub-action to trigger (e.g., `save_settings` or `update_library_status`). - `settings`: A payload containing modified configuration data. 3. Execution: Because the handler lacks a `current_user_can('manage_options')` check, the plugin executes the requested sub-action on behalf of the unauthenticated user, allowing for unauthorized configuration changes.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.