各位师傅,大师傅好,我是Darker,这次为大家带来一个Nday的CMS漏洞复现以及详细的环境搭建到代码审计流程,还希望大家或可以学到新东西,或复习旧知识,或留言反馈拷打还在成长路上的我们。
首先打开Zoomeye,去看一下这个PublicCMS的资产数量哈,百度搜索钟馗之眼官网:
网址:https://www.zoomeye.org/
登录
Zoomeye的搜索模式大概为以下几种:
【1】最简单的做法,直接输入单词回车就行了
比如,在输入框内输入我们的主角“PublicCMS”
【2】按国家 / 地区搜索
使用country关键字
【3】按照组件和版本搜索
比如这里搜索一下apache,使用关键字app: ver:分别指定web组件和版本
看了一下publiccms的资产,还是蛮多的,哈哈哈哈哈哈哈哈哈哈哈,好了,那接下来开始进入正题吧
先去github上捞一份最新的源码下来
https://github.com/sanluan/PublicCMS
用咱们的IDEA打开
//修复前
//修复后
构建完成之后,要把工作目录working directory要设置成publiccms-parent下的publiccms。
打开小皮面板,然后这里开启一个默认的mysql数据库即可,稍后web服务开启来之后,需要创建数据库。
启动应用程序!
访问127.0.0.1:8080/会跳转到127.0.0.1:8080/install/
完成初始化安装,这边用phpstudy开启一个mysql数据库,然后创建一个publiccms的数据库,数据库名cms,密码cmscms,然后完成系统的用户密码创建admin/admin,然后登录
登录进入,漏洞点位于站点的执行脚本处,在此处随便执行一个类型的脚本然后抓包
然后这边我们可以看到接口是execScript,然后提交请求方式是POST,我们去源码中搜寻是否有@RequestMapping(“execScript”)或者@PostMapping(“execScript”)
解释一下其中的注解:
@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,结合注解,我想知道具体传进来的值是什么,于是我选择在这边打断点进行动态调试查看:
这样好像也看不出来是哪里传进来的,但是可以确定数据包内的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方法中:
跟进execute方法中:
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的,所以继续往下走
步过,到这一步
将传入的parameters与正则匹配,进行参数安全校验,如果没有匹配中则将信息设置为空,如果匹配上了则将该值传入数组,这里应该就是在parameters中的数值,可以拿单个也可以拿多个,由parameters的元素个数决定循环
^:表示匹配字符串的开头。
[a-zA-Z0-9]:表示匹配一个字母(大小写不限)或一个数字。
[a-zA-Z0-9_-.]:表示匹配一个字母、一个数字、一个下划线、一个连字符或一个点号。
{1,191}:表示前面的模式可以重复出现 1 到 191 次。这是一个量词,用于限制匹配的长度范围。
$:表示匹配字符串的结尾。
也就是字符串必须以数字或者字母大小或小写开头,然后下一个字符可以是字母,数字,下划线,连字符,点号,同时检查结尾
然后循环结束来到下面这段:
String filepath = new StringBuilder(dir).append(“/”).append(command).toString();
创建一个路径,为dir加上command的数值
然后去创建这个文件对象,如果这个文件对象不存在调用getResourceAsStream() 方法来获取位于指定路径的资源的输入流。指定的路径是由 /script/ 和 command 构建而成的字符串输入流内容并复制到 这个文件对象中
然后此时电脑中是有这个文件的,所以继续往下走
结尾不为.sh,继续往下走
然后工具类插入数组的第一个位置(0下标),可以看到cmdarray中确实都有,然后步到我们的关键主角Runtime.getRuntime().exec(命令,环境变量,工作目录)上面
那么是否只要观察命令的组成是否可控就行了,回顾一下
首先是在这进行数组遍历校验参数合法性获取参数然后存入cmdarray,也就是说这边想要做命令拼接是不可行的(&,&&,||,|)
然后在这里插入执行的脚本文件的路径
cmdarray可控,但是没有用,做了安全校验,这里后面大家可以自己复现一下删除安全校验是否可以达到命令拼接进行任意命令执行。那我们接下来看一下filepath是否可控
这里也写死了,必须要用系统的内置脚本来进行
到这里感觉好像没操作可以进行下去了,但是我们继续去摸一下这个系统的功能,我个人一直很喜欢老祖宗的一个成语——“绝处逢生”,所以任何情况下都不要放弃可能的尝试,机遇有的时候就藏在看似绝望的局面当中。
首先在模板示例中随便选择一个模板进行创建
点击保存
然后发现有一个模板的替换功能点,诶呀,兄弟们,这不就来活了嘛。
尝试输入darker看一下效果
确实是用来替换一些模板元素中的值的,但这些模板本质上应该都是一些文本文件,那么是不是说明这个点可以用来替换文本当中的内容呢,尝试去修改我们之前提到的系统内置模板的内容
那我们来简单抓包看一下这个接口以及数据走向,点击替换
数据段部分:
_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的接口代码
打上断点
重新运行一下功能,断进来了,@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
然后我们进TemplateReplaceParameters看一下
可以看到有一个泛型类型为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
然后我们进泛型类型FileReplaceResult看一下
所以可以知道类型是类似:{path,index[]}的一个类型
注意这里path和index都是可控的
然后我们来看代码逻辑里有没有可以利用的地方,先判空,这里传值为login,不为空往下走
String filepath = siteComponent.getTemplateFilePath(site.getId(),CommonConstants.SEPARATOR);
获取要替换的模板文件的路径,调用CmsFileUtils.replaceFileList方法
跟进replaceFileList
重点来了,注意这句:
File file = Paths.get(dirPath, result.getPath()).toFile();
查找Paths.ge()后得知该方法接受多个字符串参数,并将它们连接起来形成一个完整的路径,也就是说这里可以进行路径拼接?
我们来看一下,整个路径是dirPath+_FileReplaceResult.path
dirPath是不可控的,也就是为F:datapubliccms/template/site_X/
然后会拼接上我们数据段中传入的FileReplaceResult中的path,但是此处对path没有做安全校验,那么我们是不是可以使用../等进行目录穿越然后替换脚本文件?
这边收集一下脚本模板所在的路径:
那就只要返回上两级目录就够了,所以我的path中传入../../script/sync.bat
变成F:datapubliccms/template/site_X/../../script/sync.bat
好了,那现在路径可控了,再看看可写方面
这段代码是获取拼接之后的路径的文件,然后逐行读取,并且把逐行读取的文件放入一个列表中,如果有一行里是包含了数据段中传入的word,那么就先判断传入的泛型列表中的index部分的长度有没有大于0,并且是从0开始的,然后在对应的行内用replace替换word,然后修改回list文件列表中,然后最后把文件列表输出回文件
那么其实我们这边数据写入也是可控的,我们只要把要替换的word设置为脚本里随便一句话:
这里设置成word=echo “repo not config!”
然后replace设置成echo "repo not config!" & start calc,如果这里失败了,试着将空格和&进行URL编码
replaceList[0].path=../../script/sync.bat
replaceList[0].indexs=0替换一次就够了
来,我们上实战看一下
确实成功修改了脚本文件,那我们这里来写一下计算器调用
然后我们到web上调用一下这个sync.bat脚本
芜湖!可以轻松愉快getshell啦~
好啦,感谢一直坚持收看到最后, 那这次也拜托大家多多支持转发收藏扩散啦,有任何问题欢迎大家积极沟通哇!!!
那好了,这里是HexaGoners六边形者实验室,我们下次再见。
原文始发于微信公众号(实战安全研究):钟馗之眼与PublicCMS(RCE)代码审计复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论