CVE-2026-39486

Download Monitor <= 5.1.8 - Authenticated (Contributor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
5.1.9
Patched in
22d
Time to patch

Description

The Download Monitor plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 5.1.8 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 contributor-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<=5.1.8
PublishedMarch 25, 2026
Last updatedApril 15, 2026
Affected plugindownload-monitor

What Changed in the Fix

Changes introduced in v5.1.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-39486 - Download Monitor SQL Injection ## 1. Vulnerability Summary The Download Monitor plugin (<= 5.1.8) is vulnerable to an authenticated SQL injection. The issue resides in the way the plugin handles query ordering and sorting, specifically within its backwards compatib…

Show full research plan

Research Plan: CVE-2026-39486 - Download Monitor SQL Injection

1. Vulnerability Summary

The Download Monitor plugin (<= 5.1.8) is vulnerable to an authenticated SQL injection. The issue resides in the way the plugin handles query ordering and sorting, specifically within its backwards compatibility logic and shortcode processing. When a user with Contributor-level access or higher uses the [downloads] shortcode with a malicious order or orderby attribute, the input is improperly handled and concatenated into a posts_orderby filter, allowing for the injection of arbitrary SQL commands.

2. Attack Vector Analysis

  • Endpoint: The injection occurs when rendering the [downloads] shortcode. This can be triggered by a Contributor+ user creating a post/page (or previewing one) containing the shortcode.
  • Vulnerable Action: Shortcode rendering for [downloads].
  • Vulnerable Parameter: order or orderby attribute within the shortcode.
  • Authentication Level: Authenticated (Contributor+). Contributors can create and preview posts.
  • Preconditions: At least one download must exist in the system so that the shortcode's query returns results and triggers the posts_orderby logic.

3. Code Flow

  1. Entry Point: A user creates/previews a post with [downloads order="DESC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)"].
  2. Shortcode Handling: DLM_Shortcodes::downloads (in src/Shortcodes.php) parses the attributes using shortcode_atts.
  3. Repository Call: The attributes are passed to DLM_Download_Repository::retrieve().
  4. Filter Trigger: The repository applies the filter dlm_query_args.
  5. Backwards Compatibility: DLM_Backwards_Compatibility::orderby_compatibility() (in includes/backwards-compatibility/class-dlm-backwards-compatibility.php) is hooked to dlm_query_args.
  6. Filter Registration: If specific criteria are met (e.g., orderby is set to meta_value_num or order_by_count is used), it adds a filter to posts_orderby via add_filter( 'posts_orderby', array( $this, 'orderby_download_count_compatibility' ) ).
  7. Vulnerable Sink: orderby_download_count_compatibility (and associated methods) concatenates the order attribute directly into the SQL string without using $wpdb->prepare or sufficient whitelisting/escaping.
  8. Execution: WordPress executes the modified query during the post preview/render, triggering the SLEEP() command.

4. Nonce Acquisition Strategy

This vulnerability is exploited via Shortcode Rendering in a post preview. Post previews in WordPress generally do not require a plugin-specific nonce to execute shortcodes; they rely on the user's authentication cookies and the standard WordPress post.php preview mechanism.

If an AJAX endpoint (like the Reports interface) is the target instead:

  1. Navigate to the Download Monitor Reports page.
  2. Use browser_eval to extract the nonce:
    • browser_eval("window.dlm_reports_vars?.nonce")
  3. However, for a Contributor-level exploit, the shortcode preview is the most reliable and direct path.

5. Exploitation Strategy

We will use a time-based blind SQL injection via the order attribute of the [downloads] shortcode.

  1. Login: Authenticate as a Contributor.
  2. Setup: Ensure a download exists (using WP-CLI).
  3. Create Payload Post: Use the http_request tool to create a draft post containing the malicious shortcode.
  4. Execute Injection: Navigate to the preview URL of that post.
  5. Payload:
    [downloads orderby="ID" order="DESC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)"]
  6. HTTP Request (Preview):
    • Method: GET
    • URL: http://localhost:8080/?p=[POST_ID]&preview=true (Standard WP preview URL)
    • Observe: The response time should be >= 5 seconds.

6. Test Data Setup

  1. Create Download:
    wp post create --post_type=dlm_download --post_title="Test Download" --post_status=publish
  2. Create Contributor User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
  3. Create Exploit Post (as Contributor):
    wp post create --post_type=post --post_title="Exploit" --post_content='[downloads orderby="ID" order="ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)"]' --post_author=[CONTRIBUTOR_ID] --post_status=draft

7. Expected Results

  • A normal preview request should complete in < 1 second.
  • The exploit request (with the SLEEP(5) payload) should take significantly longer (approx. 5 seconds per matching row in the query, though usually limited by the shortcode's default posts_per_page).
  • The database engine executes the sleep command as part of the ORDER BY clause.

8. Verification Steps

  1. Check Query Logs: If possible, check the MySQL general log to see the executed query:
    SELECT ... ORDER BY ... ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a) ...
  2. Database state: Confirm the Contributor user exists and can view the preview.
  3. Timing Analysis: Use the time_total from the Playwright/http_request tool to confirm the delay.

9. Alternative Approaches

  • Boolean-Based: Use IF(1=1, ID, title) to see if the sort order changes, then substitute 1=1 with a subquery like (SELECT 1 FROM wp_users WHERE user_login='admin' AND user_pass LIKE '$P$%').
  • Error-Based: Use GTID_SUBSET(CONCAT(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e),1) if the plugin or environment displays database errors.
  • Reports Endpoint: If the shortcode is patched but the Reports interface is not, target admin-ajax.php?action=dlm_reports_data with a payload in the order_by or order JSON parameter. This would require a nonce usually found in dlm_reports_vars.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Download Monitor plugin for WordPress is vulnerable to authenticated SQL Injection via the 'order' attribute in the [downloads] shortcode. Contributor-level users and above can exploit this to inject arbitrary SQL commands into the ORDER BY clause of database queries, potentially leading to unauthorized data extraction.

Vulnerable Code

// src/Shortcodes.php line 408
$order          = strtoupper( $order );

---

// includes/backwards-compatibility/class-dlm-backwards-compatibility.php line 285
if ( isset( $this->filters['order'] ) ) {

	$order = $this->filters['order'];
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.8/includes/backwards-compatibility/class-dlm-backwards-compatibility.php /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.9/includes/backwards-compatibility/class-dlm-backwards-compatibility.php
--- /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.8/includes/backwards-compatibility/class-dlm-backwards-compatibility.php	2024-11-28 14:28:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.9/includes/backwards-compatibility/class-dlm-backwards-compatibility.php	2026-03-04 11:41:50.000000000 +0000
@@ -285,7 +285,9 @@
 
 		if ( isset( $this->filters['order'] ) ) {
 
-			$order = $this->filters['order'];
+			$order = in_array( strtoupper( $this->filters['order'] ), array( 'ASC', 'DESC' ), true )
+				? strtoupper( $this->filters['order'] )
+				: 'DESC';
 		}
 
 		if ( apply_filters( 'dlm_count_meta_downloads', true ) ) {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.8/src/Shortcodes.php /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.9/src/Shortcodes.php
--- /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.8/src/Shortcodes.php	2024-11-28 14:28:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/download-monitor/5.1.9/src/Shortcodes.php	2026-03-04 11:41:50.000000000 +0000
@@ -406,6 +406,7 @@
 				$exclude_tag
 			) : '';
 			$order          = strtoupper( $order );
+			$order          = in_array( $order, array( 'ASC', 'DESC' ), true ) ? $order : 'DESC';
 			$meta_key       = '';
 			$order_by_count = '';

Exploit Outline

The exploit targets the [downloads] shortcode processing logic. An attacker with Contributor-level privileges (who can create and preview posts) initiates the attack by creating a post or page containing the [downloads] shortcode with a maliciously crafted 'order' attribute. Specifically, the 'order' attribute is supplied with a value like 'DESC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)', which is concatenated into the SQL ORDER BY clause by the plugin's backwards compatibility layer for download counts. When the attacker previews the post, WordPress renders the shortcode, causing the underlying SQL query to execute the SLEEP() command, confirming the time-based injection. This technique can be extended to extract sensitive database information using boolean-based or error-based payloads.

Check if your site is affected.

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