Contents

CVE-2025-10146 Analysis & POC

A vulnerability exists in the Download Manager plugin prior to version 3.3.24. Data retrieved from GET requests is printed directly into HTML attributes, leading to reflected XSS when a user with privileges accesses a URL crafted by an attacker.

  • Local WordPress & Debugging: Local WordPress and Debugging.
  • Download Manager: v3.3.23(vulnerable) and v3.3.24(fixed)
  • diff tool: meld or any tool capable of comparing differences between two versions

Use any diff tool to compare the vulnerable and patched versions. A significant difference appears in src/Admin/views/stats/history.php.

Vulnerable code:

<?php if (!empty($user_ids)): ?>
    <div class="clear-filter">
        <?php
        $get_params_xu = $get_params;
        unset($get_params_xu['user_ids']);
        $reset_url = add_query_arg($get_params_xu, 'edit.php');
        ?>
        <a href="<?php echo $reset_url; ?>" class="clear-btn" title="<?php _e('Clear user filter', 'download-manager'); ?>">
            <i class="fas fa-times"></i>
        </a>
    </div>
<?php endif; ?>

The GET parameters are directly assigned to the <a> element’s href without any protection, creating a risk of reflected XSS.

Patched code:

<?php if (!empty($user_ids)): ?>
    <div class="clear-filter">
        <?php
        $get_params_xu = $get_params;
        unset($get_params_xu['user_ids']);
        $get_params_xu = \WPDM\__\__::sanitize_array($get_params_xu, 'safetxt');

        $reset_url = add_query_arg($get_params_xu, 'edit.php');
        ?>
        <a href="<?php echo esc_url($reset_url); ?>" class="clear-btn" title="<?php _e('Clear user filter', 'download-manager'); ?>">
            <i class="fas fa-times"></i>
        </a>
    </div>
<?php endif; ?>

Patch Diff
Patch Diff: sanitized GET parameters and escaped URL

The patch sanitizes the GET parameters using sanitize_array and uses esc_url to escape the URL, making it safe.

We need to identify the real URL to inject GET parameters containing the XSS payload.

src/Admin/views/stats/history.php contains PHP and HTML tags inside the views folder, indicating it is included somewhere in the plugin.

Searching for history.php in the plugin directory:

Search 1
Search results for history.php

👉 No direct matches are found. It is likely included dynamically like {$file_name}.php. Using regex \{.*\}\.php:

Regex
Regex search for dynamic includes

src/Admin/views/stats/history.php is included in src/Admin/views/stats.php.

The actual URL to access is declared at the top of stats.php:

$base_page_uri = "edit.php?post_type=wpdmpro&page=wpdm-stats";

Similarly, stats.php is also included elsewhere, but it’s not necessary to trace further since the URL is identified.

To verify, we set a breakpoint in src/Admin/views/stats/history.php, start debugging, and access the URL:

edit.php?post_type=wpdmpro&page=wpdm-stats

Debug
Breakpoint debugging to observe GET params

First, the plugin collects all GET parameters into $get_params.

If $user_ids is not empty, a “Clear filter” button is created. To make the button functional, it must generate a URL without the user_ids parameter:

$get_params_xu = $get_params;        // copy all GET parameters
unset($get_params_xu['user_ids']);   // remove user_ids from array
$reset_url = add_query_arg($get_params_xu, 'edit.php'); // generate new URL
  • unset($get_params_xu['user_ids']) removes the filter.
  • add_query_arg($get_params_xu, 'edit.php') generates the URL with remaining parameters.

This URL is attached to the “Clear filter” button:

<a href="<?php echo $reset_url; ?>" class="clear-btn">...</a>

Result: Clicking the button reloads the page without the user_ids filter.

Send a GET request containing the XSS payload:

GET /wp-admin/edit.php?post_type=wpdmpro&page=wpdm-stats&user_ids[0]=1&payload="></a><script>alert(document.domain)</script> HTTP/1.1

The "></a> closes the <a> tag, followed by a <script> to trigger alert().

XSS
Reflected XSS triggered via GET parameter

👉 XSS succeeds. When a user with privileges accesses the attacker-provided URL, reflected XSS occurs, simulating an Unauthenticated attack scenario.

The vulnerability CVE-2025-10146 in WordPress Download Manager <= 3.3.23 is a reflected XSS, occurring when GET request data is printed directly into an HTML attribute without sanitization or escaping. Privileged users accessing the attacker-crafted URL may be exploited.

The patch in 3.3.24:

  1. Sanitizes GET parameters (sanitize_array) to remove dangerous characters.
  2. Escapes URL (esc_url) before rendering in HTML to prevent XSS.

Key takeaways:

  • Always sanitize and escape user input before rendering in HTML.
  • Check user permissions before processing or displaying sensitive data.
  • Low-privilege roles (like contributor) can become attack vectors if the plugin does not enforce proper protections.
  • Updating plugins regularly is a simple and effective defense.

Cross-site scripting (XSS) cheat sheet

WordPress Download Manager Plugin <= 3.3.24 is vulnerable to Cross Site Scripting (XSS)

Related Content