钟馗之眼与PublicCMS(RCE)代码审计复现

admin 2023年12月14日14:32:38评论24 views字数 6904阅读23分0秒阅读模式

    各位师傅,大师傅好,我是Darker,这次为大家带来一个Nday的CMS漏洞复现以及详细的环境搭建到代码审计流程,还希望大家或可以学到新东西,或复习旧知识,或留言反馈拷打还在成长路上的我们。

钟馗之眼与PublicCMS(RCE)代码审计复现

    首先打开Zoomeye,去看一下这个PublicCMS的资产数量哈,百度搜索钟馗之眼官网:

    网址:https://www.zoomeye.org/

钟馗之眼与PublicCMS(RCE)代码审计复现

    登录

钟馗之眼与PublicCMS(RCE)代码审计复现

    Zoomeye的搜索模式大概为以下几种:

【1】最简单的做法,直接输入单词回车就行了  

    比如,在输入框内输入我们的主角“PublicCMS”

钟馗之眼与PublicCMS(RCE)代码审计复现

【2】按国家 / 地区搜索

    使用country关键字

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

【3】按照组件和版本搜索

    比如这里搜索一下apache,使用关键字app: ver:分别指定web组件和版本

钟馗之眼与PublicCMS(RCE)代码审计复现

    看了一下publiccms的资产,还是蛮多的,哈哈哈哈哈哈哈哈哈哈哈,好了,那接下来开始进入正题吧

    先去github上捞一份最新的源码下来

    https://github.com/sanluan/PublicCMS

钟馗之眼与PublicCMS(RCE)代码审计复现

    用咱们的IDEA打开

    然后,最新版本中的漏洞被修复了,这里将CmsFileUtils文件中的代码,修改为漏洞修复前代码
    文件路径:publiccms-parent/publiccms-core/src/main/java/com/publiccms/common/tools/CmsFileUtils.java

//修复前

File file = Paths.get(dirPath, result.getPath()).toFile();

//修复后

//File file = Paths.get(dirPath, CmsFileUtils.getSafeFileName(result.getPath())).toFile();

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后使用maven自动构建依赖,IDEA2022自带maven,如果嫌弃maven下载速度太慢,可以去设置里面找到默认的maven配置文件,去添加国内镜像,这些就麻烦大家自行百度啦。

钟馗之眼与PublicCMS(RCE)代码审计复现


    构建完成之后,要把工作目录working directory要设置成publiccms-parent下的publiccms。

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    配置完成之后找到下图路径,可以准备启动应用程序啦,但是还需要准备一个MYSQL的数据库,这个就需要我们的小皮面板了。

钟馗之眼与PublicCMS(RCE)代码审计复现

    打开小皮面板,然后这里开启一个默认的mysql数据库即可,稍后web服务开启来之后,需要创建数据库。

钟馗之眼与PublicCMS(RCE)代码审计复现

    启动应用程序!

    访问127.0.0.1:8080/会跳转到127.0.0.1:8080/install/

    完成初始化安装,这边用phpstudy开启一个mysql数据库,然后创建一个publiccms的数据库,数据库名cms,密码cmscms,然后完成系统的用户密码创建admin/admin,然后登录

钟馗之眼与PublicCMS(RCE)代码审计复现

    登录进入,漏洞点位于站点的执行脚本处,在此处随便执行一个类型的脚本然后抓包

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后这边我们可以看到接口是execScript,然后提交请求方式是POST,我们去源码中搜寻是否有@RequestMapping(“execScript”)或者@PostMapping(“execScript”)

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    解释一下其中的注解:

    @Csrf:在 Spring Security 中,@Csrf注解是用于启用 CSRF(Cross-Site Request Forgery)保护的注解。CSRF 是一种常见的网络攻击,攻击者通过伪装成合法用户的请求来执行恶意操作。

    通过在 Spring Boot 项目中使用@Csrf 注解,你可以启用 Spring Security 对 CSRF 攻击的保护机制。当该注解被应用于 Web 安全配置类或者方法上时,Spring Security 将会自动添加 CSRF 保护措施,防止跨站请求伪造攻击。

    @RequestAttritube:用于从请求中获取特定属性的值,并将其绑定到控制器方法的参数上。

    @SessionAttritube:将特定属性的值从会话中获取,并将其绑定到控制器方法的参数上。

    但是这边数据包中并没有site和admin,但是有command和parameters,结合注解,我想知道具体传进来的值是什么,于是我选择在这边打断点进行动态调试查看:

钟馗之眼与PublicCMS(RCE)代码审计复现

    这样好像也看不出来是哪里传进来的,但是可以确定数据包内的command和parameters就是对应具体的两个参数的,所以应该是拿的之前请求中设置的值,比如下列使用@RequestAttritube的例子:

@Controller

public class TestController {

@RequestMapping(value = "/test")

public String go(HttpServletRequest request)

{

    request.setAttribute("msg","success");

    request.setAttribute("code",250);

    return "forward:/success";

}

@ResponseBody

@RequestMapping(value = "/success")

public Map test(@RequestAttribute("msg") String msg,@RequestAttribute("code") Integer code)

    {

    Map <String,Integer> map = new HashMap<>();

    map.put(msg,code);

    return map;

    }

}

    这里小小说远了,然后回到接口处理方法代码,单步过两步,来到我们接口数据包传入数据的第一条处理语句,是传入一个execute方法中:

钟馗之眼与PublicCMS(RCE)代码审计复现

    跟进execute方法中:

钟馗之眼与PublicCMS(RCE)代码审计复现

            If(CommonUtils.notEmpty(command)&&ArrayUtils,contains(COMMANDS,command.toLowerCase()))

    这句话表示首先判空然后接着看把command的值小写之后是否包含在规定的COMMANDS数组中,这边我们看到都是符合的,所以继续往下走

    然后创建了一个路径,一个数组

    然后是到一个IF中,如果传入的command是backupdb.bat或者backup.sh,那么就进入,否则的话到下面的代码中,这里这块估计是对backup.bat的操作判断,从web的功能点触发,这边可能就是对数据库的一个备份操作,然后去调用一个脚本,那这个脚本是不是要获取数据库的相关信息,所以这段代码的大概意思就是去看如果用户选择的是备份脚本,那么先加载数据库配置文件:

String databaseConfiFile = CommonConstants.CMS_FILEPATH+CmsDataSource.DATABASE_CONFIG_FILENAME;

Properties dbconfigProperties = CmsDataSource.loadDatabaseConfig(databaseConfiFile);

    然后获取数据库的用户名/数据库名/密码/加密密码,然后判断如果有加密密码,解密之后选择解密完成的密码,最后传入一个信息数组中cmdarray。

    然后我们可以从调试中看到确实也是false的,所以继续往下走

钟馗之眼与PublicCMS(RCE)代码审计复现

    步过,到这一步

钟馗之眼与PublicCMS(RCE)代码审计复现

    将传入的parameters与正则匹配,进行参数安全校验,如果没有匹配中则将信息设置为空,如果匹配上了则将该值传入数组,这里应该就是在parameters中的数值,可以拿单个也可以拿多个,由parameters的元素个数决定循环

钟馗之眼与PublicCMS(RCE)代码审计复现

^:表示匹配字符串的开头。

[a-zA-Z0-9]:表示匹配一个字母(大小写不限)或一个数字。

[a-zA-Z0-9_-.]:表示匹配一个字母、一个数字、一个下划线、一个连字符或一个点号。

{1,191}:表示前面的模式可以重复出现 1 到 191 次。这是一个量词,用于限制匹配的长度范围。

$:表示匹配字符串的结尾。

    也就是字符串必须以数字或者字母大小或小写开头,然后下一个字符可以是字母,数字,下划线,连字符,点号,同时检查结尾

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后循环结束来到下面这段:

钟馗之眼与PublicCMS(RCE)代码审计复现

String filepath = new StringBuilder(dir).append(“/”).append(command).toString();

创建一个路径,为dir加上command的数值

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后去创建这个文件对象,如果这个文件对象不存在调用getResourceAsStream() 方法来获取位于指定路径的资源的输入流。指定的路径是由 /script/ 和 command 构建而成的字符串输入流内容并复制到 这个文件对象中

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后此时电脑中是有这个文件的,所以继续往下走

钟馗之眼与PublicCMS(RCE)代码审计复现

    结尾不为.sh,继续往下走

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后工具类插入数组的第一个位置(0下标),可以看到cmdarray中确实都有,然后步到我们的关键主角Runtime.getRuntime().exec(命令,环境变量,工作目录)上面

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    那么是否只要观察命令的组成是否可控就行了,回顾一下

    首先是在这进行数组遍历校验参数合法性获取参数然后存入cmdarray,也就是说这边想要做命令拼接是不可行的(&,&&,||,|)

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后在这里插入执行的脚本文件的路径

钟馗之眼与PublicCMS(RCE)代码审计复现

    cmdarray可控,但是没有用,做了安全校验,这里后面大家可以自己复现一下删除安全校验是否可以达到命令拼接进行任意命令执行。那我们接下来看一下filepath是否可控

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    这里也写死了,必须要用系统的内置脚本来进行

    到这里感觉好像没操作可以进行下去了,但是我们继续去摸一下这个系统的功能,我个人一直很喜欢老祖宗的一个成语——“绝处逢生”,所以任何情况下都不要放弃可能的尝试,机遇有的时候就藏在看似绝望的局面当中。

    首先在模板示例中随便选择一个模板进行创建

钟馗之眼与PublicCMS(RCE)代码审计复现

    点击保存

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后发现有一个模板的替换功能点,诶呀,兄弟们,这不就来活了嘛。

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    尝试输入darker看一下效果

钟馗之眼与PublicCMS(RCE)代码审计复现

    确实是用来替换一些模板元素中的值的,但这些模板本质上应该都是一些文本文件,那么是不是说明这个点可以用来替换文本当中的内容呢,尝试去修改我们之前提到的系统内置模板的内容

    那我们来简单抓包看一下这个接口以及数据走向,点击替换

钟馗之眼与PublicCMS(RCE)代码审计复现

数据段部分:

_csrf=94194c45-f79c-4a3b-810b-bac2ebb5e307&word=login&replace=login&replaceList%5B0%5D.path=%2Fmember%2Flogin.html&replaceList%5B0%5D.indexs=0&replaceList%5B0%5D.indexs=1&replaceList%5B0%5D.indexs=2&replaceList%5B0%5D.indexs=3

URL解码之后的结果:

_csrf=94194c45-f79c-4a3b-810b-bac2ebb5e307

word=login

replace=login

replaceList[0].path=/member/login.html

replaceList[0].indexs=0

replaceList[0].indexs=1

replaceList[0].indexs=2

replaceList[0].indexs=3

    从数据段,可以看到word是搜索的字段,replace是替换的值,其余可能代表的是要替换的位置,接着我们去找这个replace的接口代码

钟馗之眼与PublicCMS(RCE)代码审计复现

    打上断点

钟馗之眼与PublicCMS(RCE)代码审计复现

    重新运行一下功能,断进来了,@ModelAttribute TemplateReplaceParameters replaceParameters, String word, String replace为数据包里对应的:

word=login,

replace=login,

replaceParameters=replaceList[0].path=/member/login.html

replaceList[0].indexs=0

replaceList[0].indexs=1

replaceList[0].indexs=2

replaceList[0].indexs=3

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后我们进TemplateReplaceParameters看一下

钟馗之眼与PublicCMS(RCE)代码审计复现

    可以看到有一个泛型类型为FileReplaceResult的List集合属性,且命名为replaceList,那么在使用@ModelAttritube之后,MVC会自动进行参数数据绑定,实例化对象,也就是说数据段中的replaceList的值都会赋值到replaceParameters 这个对象中的replaceList属性当中,这个是Spring MVC开发中的约定俗成,没什么好说的。

replaceList[0].path=/member/login.html

replaceList[0].indexs=0

replaceList[0].indexs=1

replaceList[0].indexs=2

replaceList[0].indexs=3

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后我们进泛型类型FileReplaceResult看一下

钟馗之眼与PublicCMS(RCE)代码审计复现

    所以可以知道类型是类似:{path,index[]}的一个类型

    注意这里path和index都是可控的

    然后我们来看代码逻辑里有没有可以利用的地方,先判空,这里传值为login,不为空往下走

钟馗之眼与PublicCMS(RCE)代码审计复现

String filepath = siteComponent.getTemplateFilePath(site.getId(),CommonConstants.SEPARATOR);

    获取要替换的模板文件的路径,调用CmsFileUtils.replaceFileList方法

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    跟进replaceFileList

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    重点来了,注意这句:

    File file = Paths.get(dirPath, result.getPath()).toFile();

    查找Paths.ge()后得知该方法接受多个字符串参数,并将它们连接起来形成一个完整的路径,也就是说这里可以进行路径拼接?

    我们来看一下,整个路径是dirPath+_FileReplaceResult.path

    dirPath是不可控的,也就是为F:datapubliccms/template/site_X/

    然后会拼接上我们数据段中传入的FileReplaceResult中的path,但是此处对path没有做安全校验,那么我们是不是可以使用../等进行目录穿越然后替换脚本文件?

    这边收集一下脚本模板所在的路径:

钟馗之眼与PublicCMS(RCE)代码审计复现

那就只要返回上两级目录就够了,所以我的path中传入../../script/sync.bat

变成F:datapubliccms/template/site_X/../../script/sync.bat

好了,那现在路径可控了,再看看可写方面

钟馗之眼与PublicCMS(RCE)代码审计复现

     这段代码是获取拼接之后的路径的文件,然后逐行读取,并且把逐行读取的文件放入一个列表中,如果有一行里是包含了数据段中传入的word,那么就先判断传入的泛型列表中的index部分的长度有没有大于0,并且是从0开始的,然后在对应的行内用replace替换word,然后修改回list文件列表中,然后最后把文件列表输出回文件

    那么其实我们这边数据写入也是可控的,我们只要把要替换的word设置为脚本里随便一句话:

钟馗之眼与PublicCMS(RCE)代码审计复现

    这里设置成word=echo “repo not config!”

    然后replace设置成echo "repo not config!" & start calc,如果这里失败了,试着将空格和&进行URL编码

    replaceList[0].path=../../script/sync.bat

    replaceList[0].indexs=0替换一次就够了

    来,我们上实战看一下

钟馗之眼与PublicCMS(RCE)代码审计复现

 

钟馗之眼与PublicCMS(RCE)代码审计复现

    确实成功修改了脚本文件,那我们这里来写一下计算器调用

钟馗之眼与PublicCMS(RCE)代码审计复现

钟馗之眼与PublicCMS(RCE)代码审计复现

    然后我们到web上调用一下这个sync.bat脚本

钟馗之眼与PublicCMS(RCE)代码审计复现

    芜湖!可以轻松愉快getshell啦~

钟馗之眼与PublicCMS(RCE)代码审计复现


好啦,感谢一直坚持收看到最后, 那这次也拜托持转发收藏扩散啦,有任何问题欢迎大家积极沟通哇!!!


    那HexaGoners六边形者





原文始发于微信公众号(实战安全研究):钟馗之眼与PublicCMS(RCE)代码审计复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月14日14:32:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   钟馗之眼与PublicCMS(RCE)代码审计复现https://cn-sec.com/archives/2297565.html

发表评论

匿名网友 填写信息