漏洞编号

CVE-2025-10747

漏洞描述

访问URL:https://nvd.nist.gov/vuln/detail/CVE-2025-10747

WordPress和WordPress plugin都是WordPress基金会的产品,WordPress是一套使用PHP语言开发的博客平台,该平台具有在基于PHP和MySQL的服务器上架设个人博客网站的功能。

WordPress plugin WP-DownloadManager 1.68.11及之前版本存在任意文件上传漏洞,该漏洞源于“download-add.php”文件未正确验证文件类型,具有管理员权限或更高访问权限的的攻击者可利用该漏洞上传任何恶意文件,从而远程执行恶意代码。

受影响版本

WordPress plugin WP-DownloadManager <= 1.68.11

环境搭建

Apache    V2.4.39
WordPress	V5.8.6
PHP    V7.4.3
Mysql  V8.0.11

WordPress plugin WP-DownloadManager	 V1.68.10
插件下载链接:
https://downloads.wordpress.org/plugin/wp-downloadmanager.1.68.10.zip

安装WordPress

  1. 官网下载WordPress

访问URL: https://wordpress.org/wordpress-5.8.6.zip

  1. 创建数据库wp,导入数据库sql语句,生成对应表

  1. 修改源码wp-config文件,连接数据库

  1. 启动服务及数据库,填写基本配置数据,完成后到登录页面

WordPress安装完成!

安装插件

  1. 将插件放入\wp-content\plugins文件夹

  2. 登录站点后台,启动插件

启动后出现Downloads模块

配置文件上传的文件夹,默认在files文件夹,网站路径没有需要手动创建

插件安装成功!

代码分析

1、打开项目源码进入插件所在代码

2、根据NVD通报漏洞信息,由于文件download-add.php存在安全问题,直接分析该文件代码

在这段代码中:

case0:表示从服务器已有文件中选择;

case1: 表示用户上传本地文件;

case 2:用户输入远程URL地址加载

(1)分析case0代码:

case 0:
    $file = ! empty( $_POST['file'] ) ? addslashes( wp_kses_post( trim( $_POST['file'] ) ) ) : '';
    $file = download_rename_file($file_path, $file);
    $file_size = filesize($file_path.$file);
    break;

这段代码获取本地服务器指定文件夹下的文件,无法上传文件,不存在上传问题。

(2)分析"case1" 代码:

case 1:
  if( $_FILES['file_upload']['size'] > get_max_upload_size() ) {
  $text = '<p style="color: red;">'.sprintf(__('File Size Too Large. Maximum Size Is %s', 'wp-downloadmanager'), format_filesize(get_max_upload_size())).'</p>';
  break;
} else {
  if(is_uploaded_file($_FILES['file_upload']['tmp_name'])) {
    $file_upload_to = ! empty( $_POST['file_upload_to'] ) ? $_POST['file_upload_to'] : '';
    if( $file_upload_to !== '/' ) {
      $file_upload_to = $file_upload_to . '/';
    }
    if(move_uploaded_file($_FILES['file_upload']['tmp_name'], $file_path.$file_upload_to.basename($_FILES['file_upload']['name']))) {
      $file = $file_upload_to.basename($_FILES['file_upload']['name']);
      $file = download_rename_file($file_path, $file);
      $file_size = filesize($file_path.$file);
    } else {
      $text = '<p style="color: red;">'.__('Error In Uploading File', 'wp-downloadmanager').'</p>';
      break;
    }
  } else {
    $text = '<p style="color: red;">'.__('Error In Uploading File', 'wp-downloadmanager').'</p>';
    break;
  }
}
break;
  • 函数get_max_upload_size()判断文件大小。

  • 函数is_uploaded_file()判断是否为HTTP POST方法上传。

  • 函数move_uploaded_file()将文件下载保存至web文件夹里。

该段代码对上传的文件缺乏校验,没有进行重命名、拓展名过滤等操作,这就导致了任意文件上传漏洞。

(3)分析“case2”代码

case 2:
    $file = ! empty( $_POST['file_remote'] ) ? esc_url_raw( $_POST['file_remote'] ) : '';
    if ( is_file_remote_valid( $file ) ) {
      $file_size = remote_filesize( $file );
    } else {
      $text = '<p style="color: red;">' . __( 'There Is An Error Parsing Remote File URL', 'wp-downloadmanager' ) . '</p>';
    }
    break;

跟一下函数is_file_remote_valid(),这个函数判断URL是否合法

  • parse_url()函数将url转化为数字,按照协议、域名、端口、路径划分

  • 首先校验是否为一个数组,判断URL完整性

  • 再使用协议白名单判断是否为合法的协议

  • 最后使用端口号白名单,判断是否为正常的端口

目前来看,没有文件过滤,要是否存在问题,就需要看加载后文件在什么位置。

再跟一下函数remote_filesize();

该函数只访问了文件获取其大小,并未下载文件到服务器。

继续看代码

这里直接使用file(远程加载URL)为文件名直接存入数据库,没有将文件下载到WEB站点目录下。所以“case2”不存在漏洞问题。

综上,三个功能点中只有“case1”代码存在任意文件上传漏洞。

漏洞复现

  1. 登录后台,选择本地上传文件

  1. 上传成功,这里文件ID=9

  1. 在主题里配置下载按钮,点击更新

  1. 前端访问页面,出现下载按钮

  1. 按文件实际路径直接访问

恶意phpinfo.php文件被执行。

修复措施

厂商尚未提供漏洞修复方案,请关注厂商主页更新:

https://wordpress.org/plugins/wp-downloadmanager/advanced/

或者可以按以下方式修改代码(仅供参考)

case 1:
  // 1. 检查是否有上传错误或大小超限
  if (empty($_FILES['file_upload']['tmp_name']) || $_FILES['file_upload']['error'] !== UPLOAD_ERR_OK) {
  $text = '<p style="color: red;">' . __('Invalid file upload.', 'wp-downloadmanager') . '</p>';
  break;
}

if ($_FILES['file_upload']['size'] == 0) {
  $text = '<p style="color: red;">' . __('Uploaded file is empty.', 'wp-downloadmanager') . '</p>';
  break;
}

if ($_FILES['file_upload']['size'] > get_max_upload_size()) {
  $text = '<p style="color: red;">' . sprintf(
    __('File size too large. Maximum allowed: %s', 'wp-downloadmanager'),
    format_filesize(get_max_upload_size())
  ) . '</p>';
  break;
}

// 2. 验证是合法上传文件
if (!is_uploaded_file($_FILES['file_upload']['tmp_name'])) {
  $text = '<p style="color: red;">' . __('Invalid upload attempt.', 'wp-downloadmanager') . '</p>';
  break;
}

// 3. 获取目标子目录
$upload_subdir = !empty($_POST['file_upload_to']) ? trim($_POST['file_upload_to']) : '';
$upload_dir = trailingslashit($file_path) . ltrim($upload_subdir, '/') . '/';

// 4. 创建目录(递归)
if (!is_dir($upload_dir)) {
  if (!mkdir($upload_dir, 0755, true)) {
    $text = '<p style="color: red;">' . __('Failed to create upload directory.', 'wp-downloadmanager') . '</p>';
    break;
  }
}

// 5. 提取文件信息并校验扩展名
$raw_filename = basename($_FILES['file_upload']['name']);
$file_ext     = strtolower(pathinfo($raw_filename, PATHINFO_EXTENSION));
$safe_filename = pathinfo($raw_filename, PATHINFO_FILENAME); // 去掉后缀

// 白名单扩展名过滤
$allowed_extensions = array(
  'jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx',
  'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar'
);

if (!in_array($file_ext, $allowed_extensions)) {
  $text = '<p style="color: red;">' . sprintf(
    __('File type ".%s" is not allowed. Only %s are permitted.', 'wp-downloadmanager'),
    esc_html($file_ext),
    implode(', ', $allowed_extensions)
  ) . '</p>';
  break;
}

// 6. 强制重命名防止冲突和猜测
$new_filename = 'upload_' . uniqid() . '.' . $file_ext;
$target_path  = $upload_dir . $new_filename;

// 7. 移动上传文件
if (move_uploaded_file($_FILES['file_upload']['tmp_name'], $target_path)) {
  // 成功后记录相对路径
  $file = str_replace(trailingslashit($file_path), '', $target_path);
  $file_size = filesize($target_path);
}
break;

在/wp-content/files/.htaccess文件添加:

# 禁止执行脚本文件
<Files "*.php">
    Order Deny,Allow
    Deny from all
</Files>