VvvebCMS任意文件上传导致RCE|CVE-2026-6257原理分析&研究

admin 2026-04-29 05:08:11 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细分析了VvvebCMS1.0.8及以下版本中存在的任意文件上传漏洞CVE-2026-6257。漏洞核心在于media.php文件的rename()函数验证逻辑缺陷:检测到危险扩展名时仅设置失败标志但未中断执行,导致攻击者可绕过限制重命名文件。通过先上传.htaccess文件注入Apache指令,再上传PHP文件实现远程代码执行。文档提供了完整的Docker环境搭建步骤、手动/自动化复现方法、流量特征分析以及修复方案。 综合评分: 85 文章分类: 漏洞分析,WEB安全,应用安全,红队,应急响应


cover_image

Vvveb CMS 任意文件上传导致RCE | CVE-2026-6257原理分析&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年4月27日 22:46 北京

在小说阅读器读本章

去阅读

点击蓝字,关注我们

0x0 背景介绍

Vvveb是一套开源的PHP内容管理系统,用于构建网站、博客或电商平台。

受影响版本中,media.php文件的rename()函数在检测到受限扩展名(如.php、.htaccess)时,仅设置$success=false但缺少return语句,导致验证失败后仍继续执行重命名操作。攻击者可利用此逻辑缺陷先将文本文件重命名为.htaccess注入Apache指令,再将另一文件重命名为.php从而执行任意操作系统命令。

修复版本中通过在扩展名检测逻辑后添加return语句并提前设置响应类型为json,使验证失败时立即返回错误响应并终止函数执行。

漏洞详情

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | 文件上传 | <= 1.0.8 | 低 | CVE-2026-6257 |

攻击效果:

  • 任意文件上传,导致RCE。

0x1 环境搭建(Ubuntu24)

1.1-Ubuntu24+Docker搭建配置

  • ### 另存为install.sh
#!/bin/bash# 若任何命令失败则退出set&nbsp;-eecho&nbsp;"[*] 阶段1/4:检查并安装基础依赖..."if&nbsp;!&nbsp;command&nbsp;-v docker &> /dev/null || !&nbsp;command&nbsp;-v git &> /dev/null || !&nbsp;command&nbsp;-v curl &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] 正在安装 docker.io git curl ..."&nbsp; &nbsp;&nbsp;sudo&nbsp;apt update &&&nbsp;sudo&nbsp;apt install -y docker.io git curlfisudo&nbsp;systemctl&nbsp;enable&nbsp;--now docker 2>/dev/null ||&nbsp;trueecho&nbsp;"[*] 阶段2/4:创建工作目录并克隆 Vvveb 仓库(含子模块)..."WORKDIR="$HOME/vvveb-lab-git"mkdir&nbsp;-p&nbsp;"$WORKDIR"&nbsp;&&&nbsp;cd&nbsp;"$WORKDIR"if&nbsp;[ -d Vvveb ];&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[!] 目标目录 Vvveb 已存在,将进行清理..."&nbsp; &nbsp;&nbsp;rm&nbsp;-rf Vvvebfigit&nbsp;clone&nbsp;--recurse-submodules https://github.com/givanz/Vvveb.gitcd&nbsp;Vvveb# 切换到稳定版本 1.0.8git checkout 1.0.8# 确保子模块匹配标签版本git submodule update --init --recursiveecho&nbsp;"[+] Vvveb 1.0.8 项目已准备完毕(含完整主题)。"echo&nbsp;"[*] 阶段3/4:生成 docker-compose.yml 配置文件..."cat&nbsp;> docker-compose.yml <<'EOF'version:&nbsp;'3.8'services:&nbsp; web:&nbsp; &nbsp; image: php:8.1-apache&nbsp; &nbsp; container_name: vvveb_web&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - .:/var/www/html&nbsp; &nbsp; ports:&nbsp; &nbsp; &nbsp; -&nbsp;"8080:80"&nbsp; &nbsp; depends_on:&nbsp; &nbsp; &nbsp; - db&nbsp; &nbsp; networks:&nbsp; &nbsp; &nbsp; - vvveb_net&nbsp; &nbsp;&nbsp;command: >&nbsp; &nbsp; &nbsp; sh -c&nbsp;"apt-get update &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;apt-get install -y libzip-dev &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;docker-php-ext-install pdo_mysql gettext mysqli zip &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;a2enmod rewrite &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;sed -i 's|DocumentRoot /var/www/html|DocumentRoot /var/www/html/public|' /etc/apache2/sites-available/000-default.conf &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;sed -i 's|AllowOverride None|AllowOverride All|g' /etc/apache2/conf-enabled/docker-php.conf &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;chown -R www-data:www-data /var/www/html &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;apache2-foreground"&nbsp; db:&nbsp; &nbsp; image: mysql:5.7&nbsp; &nbsp; container_name: vvveb_db&nbsp; &nbsp;&nbsp;command: --default-authentication-plugin=mysql_native_password --sql-mode=""&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; MYSQL_ROOT_PASSWORD: root_password&nbsp; &nbsp; &nbsp; MYSQL_DATABASE: vvveb_db&nbsp; &nbsp; &nbsp; MYSQL_USER: vvveb_user&nbsp; &nbsp; &nbsp; MYSQL_PASSWORD: vvveb_pass&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - db_data:/var/lib/mysql&nbsp; &nbsp; networks:&nbsp; &nbsp; &nbsp; - vvveb_netvolumes:&nbsp; db_data:networks:&nbsp; vvveb_net:EOFecho&nbsp;"[*] 阶段4/4:启动 Docker 容器(首次需拉取镜像并编译扩展,约1分钟)..."docker compose down -v 2>/dev/null ||&nbsp;truedocker compose up -decho&nbsp;""echo&nbsp;"=============================================="echo&nbsp;" Vvveb 1.0.8 环境部署完成!"echo&nbsp;" &nbsp;- 访问地址: http://localhost:8080"echo&nbsp;" &nbsp;- 安装向导将自动启动"echo&nbsp;" &nbsp;- 数据库信息(安装时填写):"echo&nbsp;" &nbsp; &nbsp; &nbsp;引擎:MySQLi"echo&nbsp;" &nbsp; &nbsp; &nbsp;主机:db"echo&nbsp;" &nbsp; &nbsp; &nbsp;库名:vvveb_db"echo&nbsp;" &nbsp; &nbsp; &nbsp;用户:vvveb_user"echo&nbsp;" &nbsp; &nbsp; &nbsp;密码:vvveb_pass"echo&nbsp;" &nbsp;- 管理员账号在安装时自行设置"echo&nbsp;" &nbsp;- 后台地址: http://localhost:8080/admin/"echo&nbsp;""echo&nbsp;" &nbsp;- 如有问题,可查看日志: docker logs vvveb_web"echo&nbsp;"=============================================="

0x2 漏洞复现

2.1-脚本验证

https://github.com/Kai-One001/cve-/blob/main/Vvveb_CMS_RCE_CVE-2026-6257.py

  • 脚本思路是👇:
1) 用管理员账号登录后台2) 打开媒体管理器(媒体/资源管理相关页面,内部会调用`admin/index.php?module=media/media`)3) 上传一个普通文本文件(例如a.txt)到media/根目录4) 在媒体列表中对a.txt执行重命名,改成.htaccess5) 再上传另一个文本文件(例如 shell.txt)6) 访问 https://<host>/media/shell.txt?cmd=whoami(示例)观察命令执行结果

#

2.2-手动复现

2.2.1-先上传txt文件内容为.htacces

2.2.2-对文件进行重命名

#

2.2.3-再次上传一句话

理论思路是:上传一句话txt文件,重命名为php文件配合之前重命名的.htacces进行利用,但是这里偷懒了

2.2.4-验证执行

因为.htacces有 将txt当作php执行,所以可以直接验证

#

2.2.5-主机验证

2.3-复现流量特征 (PCAP)

几个重要节点流量

  • 上传接口

  • 重命名文件

  • ## RCE验证成功

#


0x3 漏洞原理分析

3.1-架构与模块定位

Vvveb CMS 的媒体管理并不是一个“单独的上传脚本”,而是典型的Admin控制器 + Trait 复用逻辑 结构:控制器负责路由入口与视图变量,而真正的上传/删除/重命名等危险文件操作被放进system/traits/media.phpMedia trait&nbsp;里复用

这类设计的预期安全边界通常是:

1.&nbsp;入口层(Admin 控制器)保证 已登录、CSRF 校验、权限;
2.&nbsp;逻辑层(Trait)保证 文件名净化、扩展名/MIME 拦截,并且在拦截命中时 立即中断危险操作;
3.&nbsp;物理层(文件系统/Apache)按配置决定是否会解释执行。

重命名处理器里:它看似做了扩展名拦截,但缺少return/中断,导致拦截形同虚设。

| 层级 | 核心文件 | 关键职责 | 与漏洞链条的关系 | | — | — | — | — | | 入口层 | admin/controller/media/media.php | 后台媒体模块控制器;调用 setMediaEndpoints() 暴露 upload/rename 等 endpoint | 提供可被认证用户调用的上传/重命名入口 | | 逻辑层(危险操作) | system/traits/media.php | upload() /rename()/delete() 等文件系统操作;扩展名、MIME 拦截;落盘与 rename/copy | 漏洞点就在 rename():拦截命中后不终止,仍执行 rename() | | 安全边界 | admin/controller/base.php | Admin 统一初始化:登录校验、CSRF 校验、权限初始化 | 说明漏洞前提:必须已登录且携带 CSRF | | 视图/交互 | admin/template/media/media.tpl + 前端 JS(多处引用 media controller) | 管理后台界面;触发上传/重命名请求 | 用于 Web 复现时定位请求与参数 |

3.2 锁定关键路径:从“媒体重命名”一路追到文件系统 rename()

漏洞描述中提到“媒体管理功能”,而Vvveb的后台模块通常是 admin[自定义]/index.php?module=... 的路由。先确认媒体模块的入口是否把upload/rename作为可调用action暴露出来。

技巧:

• 直接在代码里搜索 module=media/media,可以看到多处控制器引用;
• 最核心的是 admin/controller/media/media.php ,里面会构造&nbsp;$controllerPath。

查到一个媒体模块控制器并不直接处理重命名,但它做了一件关键事:调用``setMediaEndpoints(),把 upload/rename 等 endpoint 拼成 URL暴露给前端。

// admin/controller/media/media.phpuseVvveb\System\Traits\Media asMediaTrait;// ...function&nbsp;index(){&nbsp;$adminPath&nbsp;= \Vvveb\adminPath();&nbsp;$controllerPath&nbsp;=&nbsp;$adminPath&nbsp;.'index.php?module=media/media';&nbsp;$this->setMediaEndpoints($controllerPath);
&nbsp;$this->view->mediaContentUrl ="$controllerPath&action=mediaContent";}// ...
  • 入口可以明确了:所有重命名都落到同一个动作action=rename,而动作实现就在同一个trait里的rename() 函数(问了下AI命名逻辑)。
  • 控制器类Media明确use MediaTrait;真正的action实现并不在控制器文件里,而在trait中
  • 接下来要找的就是:它是否真的在执行前阻断了危险扩展名。

#

3.3-边界缺失:好消息~有拦截   坏消息!没拦住

顺着setMediaEndpoints()往下看,找下endpoint ,检查是否存在upload()与 rename()

// system/traits/media.phpprotected&nbsp;function setMediaEndpoints($controllerPath) {&nbsp; &nbsp; $this->view->uploadUrl =&nbsp;"$controllerPath&action=upload";&nbsp; &nbsp; $this->view->renameUrl =&nbsp;"$controllerPath&action=rename";&nbsp; &nbsp;&nbsp;//xxxx}
  • 先注意到一个deny列表,说明代码是做限制了:
// &nbsp;system/traits/media.phppublic&nbsp;$uploadDenyExtensions&nbsp;= ['php',&nbsp;'svg',&nbsp;'js',&nbsp;'exe'];
  • 然后再看主角rename(),关键片段如下:
// &nbsp;system/traits/media.phpfunction&nbsp;rename()&nbsp;{&nbsp; &nbsp;&nbsp;$file&nbsp; &nbsp; =&nbsp;sanitizeFileName($this->request->post['file']);&nbsp; &nbsp;&nbsp;$newname&nbsp;=&nbsp;sanitizeFileName($this->request->post['newname'] ??&nbsp;'');&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;$currentFile&nbsp;=&nbsp;$dirMedia&nbsp;. DS .&nbsp;$file;&nbsp; &nbsp;&nbsp;if&nbsp;($newname) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$targetFile&nbsp; =&nbsp;dirname($currentFile) . DS .&nbsp;$newname;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;$extension&nbsp;=&nbsp;strtolower(substr($targetFile,&nbsp;strrpos($targetFile,&nbsp;'.') +&nbsp;1));
&nbsp; &nbsp;&nbsp;if&nbsp;(isset($this->uploadDenyExtensions) &&&nbsp;in_array($extension,&nbsp;$this->uploadDenyExtensions)) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;.=&nbsp;__('File type not allowed!');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$success&nbsp;=&nbsp;false;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 关键:即便命中 deny,这里也没有 return / die / output&nbsp; &nbsp;&nbsp;if&nbsp;(rename($currentFile,&nbsp;$targetFile)) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;= ['success'&nbsp;=>&nbsp;true,&nbsp;'message'&nbsp;=>&nbsp;__('File renamed!')];&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;= ['success'&nbsp;=>&nbsp;false,&nbsp;'message'&nbsp;=>&nbsp;__('Error renaming file!')];&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;$this->response->output($message);}
  • 预期设计:命中deny列表(例如目标扩展名是php)时,应当 立即中断,并返回失败响应(比如403/400或 success=false)。
  • 实际实现:命中deny后只是给$success=false,但没有任何 控制流中断,随后无条件进入rename($currentFile, $targetFile)
  • 结果:扩展名拦截从安全策略变成提示,只要底层文件系统允许重命名,后台就会给出 重命名成功的JSON。
  • 更绝的是:rename()函数中$message/$success甚至没有初始化(相比upload() 有明确初始化),这意味着“以为自己在维护一个success/message 状态机”,但真正返回给客户端的,最终会被rename() 的结果覆盖

重命名PHP后缀时,提示失败,但已完成(不过按理说可以直接执行PHP了,但是我一直403…人麻木了)

3.4-推导最大危害:从“可写文件”升级到“可执行代码”

目前来看,重命名绕过.php的理论已经成立,然后就再往下翻翻如何RCE

第一段:上传阶段的安全边界确实存在

//system/traits/media.phpif&nbsp;(isset($this->uploadDenyExtensions) &&&nbsp;in_array($extension,&nbsp;$this->uploadDenyExtensions)) {&nbsp; &nbsp;&nbsp;$message&nbsp;.=&nbsp;__('File type not allowed!');&nbsp; &nbsp;&nbsp;$success&nbsp;=&nbsp;false;}
......
if&nbsp;(isset($this->uploadDenyMime) &&&nbsp;in_array($mimeType,&nbsp;$this->uploadDenyMime)) {
&nbsp; &nbsp;&nbsp;$message&nbsp;.=&nbsp;__('File type not allowed!');
&nbsp; &nbsp;&nbsp;$success&nbsp;=&nbsp;false;
}......
if&nbsp;($success) {&nbsp; &nbsp;&nbsp;if&nbsp;($overwrite) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$destination&nbsp;=&nbsp;$this->dirMedia .&nbsp;$path&nbsp;. DS .&nbsp;$fileName;&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(file_exists($destination&nbsp;=&nbsp;$this->dirMedia .&nbsp;$path&nbsp;. DS .&nbsp;$fileName) && ($i++ <&nbsp;5)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$fileName&nbsp;=&nbsp;rand(0,&nbsp;10000) .&nbsp;'-'&nbsp;.&nbsp;$origFilename;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(@move_uploaded_file($files['tmp_name'][$count],&nbsp;$destination)) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(isset($this->request->post['onlyFilename'])) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$return&nbsp;=&nbsp;$fileName;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$return&nbsp;=&nbsp;$destination;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;=&nbsp;__('File uploaded successfully!');&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$destination&nbsp;=&nbsp;$this->dirMedia .&nbsp;$path&nbsp;. DS;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$success&nbsp; &nbsp; &nbsp;=&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!&nbsp;is_writable($destination)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;=&nbsp;sprintf(__('%s not writable!'),&nbsp;$destination);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$message&nbsp;=&nbsp;__('Error moving uploaded file!');&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}
  • upload() 会检查扩展名与 MIME:所以攻击者不能直接上传.php

第二段:重命名阶段把上传边界“拆掉” 一旦攻击者能把已上传文件重命名,媒体目录就可能出现可执行脚本。

但还差一个现实条件:服务器是否会把该目录下的.php执行?

1、在很多很多部署里,/media/ 本就位于 Web 根目录,且 PHP 解析默认对 .php 生效;这时 仅凭重命名 即可 RCE。•
2、如果运维曾做过限制,攻击者仍可通过写入 .htaccess

这里还有一个关键事实:Vvveb的deny列表里 没有htaccess。这意味着:

1、上传&nbsp;a.txt(内容为 Apache 指令)是允许的;2、重命名为&nbsp;.htaccess&nbsp;也很可能允许; 最终媒体目录的行为被攻击者“重配置”,从纯静态资源目录变成可执行载荷的容器。

3.5 链路总结(调用链 + 注入点/爆发点标注)

完整链路(以后台媒体模块为入口):

/xxx/index.php?module=media/media-> xxx/controller/media/media.php::index() &nbsp;->&nbsp;system/traits/media.php::setMediaEndpoints()`(暴露 `action=upload` / `action=rename`) &nbsp;-> [注入点] `system/traits/media.php::upload()`(允许上传文本载荷为 `.txt`) &nbsp;-> [逻辑缺陷] `system/traits/media.php::rename()`(deny 命中不 return,仍执行 `rename()`) &nbsp;-> [爆发点] Web 访问 `public/media/<payload>.php`(或受 `.htaccess` 影响的扩展)触发解释执行 &nbsp;-> RCE:以 Web 进程用户执行系统命令

#

#


0x4 修复建议

1、升级最新版本:将组件升级安全版本,v1.0.8以上版本

https://github.com/givanz/Vvveb

2、临时防护措施:

  • 服务器隔离:禁止public/media/目录下解析PHP,明确禁止.htaccess 生效:对媒体目录AllowOverride None,并阻止上传/写入以点开头的敏感文件名
  • 防火墙 / WAF:拦截/审计后台接口,且newname/newfile 以.php、.phtml、.phar、.htaccess、.user.ini结尾
  • 拦截上传问:拦截上传内容包含Apache指令关键字:AddType、SetHandler、php_value、php_flag

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

/** 狡诈的科目二!!**/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《Vvveb CMS 任意文件上传导致RCE | CVE-2026-6257原理分析&研究》

评论:0   参与:  0