java序列化与反序列化
java 序列化与反序列化 为了更好的存放传输一些数据,往往会将一些数据进行序列化,需 要用到的时候再反序列化获取对象进行操作,在 java 中只要继承 了 Serializable 接口的类就可以被序列化,根据官方文档该类 只作为序列化的标识,不用实现该接口

在序列化与反序列化的过程中其实也就是涉及到两个方法
writeObject和readObject,官方文档定义
writeObject方法负责为其特定的类编写对象的状态,以便相应的readObject方法可以恢复它。 可以通过调用out.defaultWriteObject来调用保存对象字段的默认机制。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法或通过使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
readObject方法负责从流中读取并恢复类字段。 它可以调用in.defaultReadObject来调用恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject方法使用流中的信息将保存在流中的对象的字段分配给当前对象中相应命名的字段。 当处理类进化到添加新字段时,这将处理这种情况。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法或通过使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
关于两种方法的学习
定义一个T00ls类用于序列化操作
import java.io.Serializable;
public class T00ls implements Serializable {
private int id;
private String username;
private String password;
@Override
public String toString() {
return "T00ls{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
定一个serialize类用于序列化T00ls类,生成文件T00ls.ser
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class serialize {
public static void main(String[] args) throws Exception {
//实例化T00ls类
T00ls t00ls = new T00ls();
//创建文件t00ls.ser
FileOutputStream fileOutputStream = new FileOutputStream("./t00ls.ser");
//创建object输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//将对象写入到文件t00ls.ser
objectOutputStream.writeObject(t00ls);
//关闭obejct输出流
objectOutputStream.close();
}
}

创建 unserialize类读取t00ls.ser文件反序列化得到对象,打印对象
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class unserialize {
public static void main(String[] args) throws Exception{
//创建文件输入流
FileInputStream fileInputStream = new FileInputStream("./t00ls.ser");
//创建object输入流
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//类型转换为t00ls类型
T00ls o = (T00ls)objectInputStream.readObject();
System.out.println(o);
}
}

以上就是java序列化与反序列化的过程,java的反序列化漏洞其实就是发生在readObject,
该方法如果被重写时调用了危险的方法就可导致反序列化过程中调用危险方法从而执行一些命令,简单实例在被序列化的类中重写readObject方法:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class T00ls implements Serializable {
private int id;
private String username;
private String password;
private void readObject( java.io.ObjectInputStream ob) throws IOException, ClassNotFoundException {
//执行默认的readObject方法
ob.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
@Override
public String toString() {
return "T00ls{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
反序列化时触发弹出计算器

java.net.URL 类
根据官方文档该类继承自Object类实现了Serializable接口

根据urldns的公布的利用链,最后是通过URL的hashcode方法触发的dns请求
demo代码:
import java.net.MalformedURLException;
import java.net.URL;
public class test {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://4x0cv9.dnslog.cn");
url.hashCode();
}
}
执行完成后触发了dns请求

在hashcode出打断点进行调试,进入hashcode方法,判断当前的hashcode是否为-1是的话直接返回hashcode,不是的话调用URLStreamhandler的hashCode方法

继续跟进handler的hashCode方法,该方法调用了一个getHostAddress方法来处理传进来的url

继续跟进getHostAddress,调用了InetAddress的getByName方法,官方文档该方法为解析url的ip地址,也就是触发dns请求的真正方法


ysoserial URLDNS利用链分析
该工具注释中也给出来具体的链
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
通过利用链可以看出是HashMap类重写了readObject方法是反序列化漏洞触发的前提条件

该方法最后调用了putval方法,参数key经过了hash计算

跟进hash方法,处理的时候调用了key的hashCode方法,这就很明了了,传入url对象的话就会调用url的hashCode方法触发dns请求

ysoserial payload生成过程,生成类为URLDNS.class

对该类进行调试,启动主程序GeneratePayload对main方法,在idea 设置中添加参数

设置断点开始调试

通过命令行获取到加载类名和url

获取到加载类URLDNS,创建URLDNS类实例,然后将对象进行序列化

进入到getObject方法,就来到了URLDNS类

实例化HashMap对象,生成URL对象,将url对象作为key put到hashMap对象

进入put方法,调用了hash方法来处理key

跟进hash方法,最终调用了key的hashcode方法,也就是传入的url对象的hashCode 触发dns

最后通过反射来将hashcode设置为-1,前面说了URL的hashCode等于-1是才会调用URLStreamHandler的hashcode方法

网友评论