Java反序列化技术分享(1)--基础篇
2022-9-5 09:2:16 Author: 我不是Hacker(查看原文) 阅读量:13 收藏

一、Java序列化和反序列化基础

Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化,将Java对象转为字节序列。

Java 反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。

举一个简单的例子,见代码SerializeAndDeserialize ps:这里重点关注下在代码中的强制转换类型

package org.chabug.demo;
import org.chabug.entity.Dog;import org.chabug.entity.Person;import org.chabug.util.Serializables;/*这个例子是为了证明只要实现了Serializable接口的类都可以被序列化并且Java内置的几大数据类型也可被序列化,因为他们都继承了Object类 */public class SerializeAndDeserialize {
public static void main(String[] args) throws Exception { byte[] bytes; String s1 = "I'm a String Object...."; bytes = Serializables.serializeToBytes(s1); Object o1 = Serializables.deserializeFromBytes(bytes);        System.out.println(o1);
String[] s2 = new String[]{"tom", "bob", "jack"}; bytes = Serializables.serializeToBytes(s2); String[] o2 = (String[])Serializables.deserializeFromBytes(bytes); System.out.println(o2);
int i = 123; bytes = Serializables.serializeToBytes(i); int o3 = (Integer) Serializables.deserializeFromBytes(bytes); System.out.println(o3); // 一只名叫woody的狗 Dog dog = new Dog(); dog.setName("woody"); // tom Person tom = new Person(); tom.setAge(14); tom.setName("tom"); tom.setSex("男");        tom.setDog(dog);
bytes = Serializables.serializeToBytes(tom); Person o = (Person) Serializables.deserializeFromBytes(bytes);        System.out.println(o); }}

String、Integer、数组、Object对象等Java内置的数据类型均可实现序列化,我们自己写的Person、Dog类只要实现了Serializable接口即可实现序列化和反序列化。

二、为什么在反序列化的时候会产生漏洞?

来看一段代码,现在有一个恶意的实体类EvilClass

package org.chabug.entity;
import java.io.ObjectInputStream;import java.io.Serializable;
public class EvilClass implements Serializable { String name;
public EvilClass() { System.out.println(this.getClass() + "的EvilClass()构造方法被调用!!!!!!");    }
public EvilClass(String name) { System.out.println(this.getClass() + "的EvilClass(String name)构造方法被调用!!!!!!"); this.name = name;    }
public String getName() { System.out.println(this.getClass() + "的getName被调用!!!!!!"); return name; }
public void setName(String name) { System.out.println(this.getClass() + "的setName被调用!!!!!!"); this.name = name; }
@Override public String toString() { System.out.println(this.getClass() + "的toString()被调用!!!!!!"); return "EvilClass{" + "name='" + getName() + '\'' + '}'; }
private void readObject(ObjectInputStream in) throws Exception { //执行默认的readObject()方法 in.defaultReadObject(); System.out.println(this.getClass() + "readObject()被调用!!!!!!"); Runtime.getRuntime().exec(new String[]{"cmd", "/c", name}); }}

其readObject中存在执行命令的代码

Runtime.getRuntime().exec(new String[]{"cmd", "/c", name})

name参数是要执行的命令。那么我们可以构造一个恶意的对象,将其name属性赋值为要执行的命令,当反序列化触发readObject时就会RCE。如下

package org.chabug.demo;
import org.chabug.entity.EvilClass;import org.chabug.util.Serializables;
public class EvilSerialize { public static void main(String[] args) throws Exception { EvilClass evilObj = new EvilClass(); evilObj.setName("calc"); byte[] bytes = Serializables.serializeToBytes(evilObj); EvilClass o = (EvilClass) Serializables.deserializeFromBytes(bytes); System.out.println(o); }}

那么现在我们知道了反序列化是如何被RCE的,但是开发中也不可能直接这么写,所以这就涉及到了利用链的寻找。反序列化漏洞需要三个东西

  • 反序列化入口(source)

  • 目标方法(sink)

  • 利用链(gadget chain)

细心再看上图中的输出结果,不仅仅触发了readObject方法,还触发了toString()、无参构造、set、get方法,那么在实际寻找利用链的过程中就不仅仅需要关注readObject()的方法了。

然后到现在我们就需要了解反射这个东西了,上文中我们提到了强制类型转换的问题,在实际开发中,在readObject中会进行逻辑处理,当不知道传入对象的具体数据类型时会通过反射来判断调用,而反射就是我们通向RCE的重要手段。

三、Java反射

什么是反射?"反射"中有一个"反"字,那么解释反射就得从"正射"开始,看代码。这是我的实体类

package org.chabug.entity;
import java.io.IOException;
public class ReflectionClass { String name;
public ReflectionClass(String name) { this.name = name;    }
public ReflectionClass() {    }
public String say() { return this.name;    }
private void evil(String cmd) { try { Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd}); } catch (IOException e) { e.printStackTrace(); }    }
@Override public String toString() { return "ReflectionClass{" + "name='" + name + '\'' + '}';    }
public String getName() { return name; }
public void setName(String name) { this.name = name; }}

正常写法

package org.chabug.demo;
import org.chabug.entity.ReflectionClass;
public class ReflectionDemo { public static void main(String[] args) { ReflectionClass demo = new ReflectionClass(); demo.setName("hello"); System.out.println(demo.say());// demo.evil("calc"); // 不能够调用private方法 }}

很简单就是通过new创建了一个ReflectionClass实例,然后通过实例去调用其所属方法,这就是"正射"。但是当你new的时候不知道类名怎么办?受private保护的方法怎么调用?反射的作用就体现出来了。看下面这一段代码

package org.chabug.demo;
import org.chabug.entity.ReflectionClass;
import java.lang.reflect.Method;
public class ReflectionDemo { public static void main(String[] args) throws Exception { // new Class<?> aClass = Class.forName("org.chabug.entity.ReflectionClass"); Object o = aClass.newInstance(); // setName("jack") Method setName = aClass.getDeclaredMethod("setName",String.class); setName.invoke(o, "jack"); // say() Method say = aClass.getDeclaredMethod("say",null); Object o1 = say.invoke(o, null); System.out.println(o1); // evil("calc") // 反射可以修改方法的修饰符来调用private方法 Method evil = aClass.getDeclaredMethod("evil", String.class); evil.setAccessible(true); evil.invoke(o,"calc"); }}

不需要提前知道类名,用到org.chabug.entity.ReflectionClass类改一改通过参数传进来就行了,并且可以通过setAccessible来获取private保护的方法或字段。

下一篇我们从漏洞入手,深入了解反射在反序列化中的作用,以及反序列化调用链的挖掘。

四、原文链接

https://github.com/Y4er/WebLogic-Shiro-shell

五、相关推荐

JNDI-Injection-Exploit-Plus 

  • 是一款JNDI注入利用工具,可以生成JNDI链接并启动后端相关服务

  • 是一款反序列化Payload生成工具(包含50+ Gadgets链,比ysoserial还多出10+

工具地址:https://github.com/cckuailong/JNDI-Injection-Exploit-Plus



文章来源: http://mp.weixin.qq.com/s?__biz=MzkwNDI1NDUwMQ==&mid=2247486245&idx=1&sn=cd1c6e812831177cd582b39d3b688c4c&chksm=c0888e6ff7ff0779a911307bba1af19ab22d650730916d79b6435d5699b335c9c89f8bdef812#rd
如有侵权请联系:admin#unsafe.sh