java反序列化学习

java反序列化学习

序列化:把一个对象的状态信息转换为字节序列,用于存储或者传输
反序列化:把字节序列转换为java对象
想要将一个对象序列化,必须实现Serializable或者Externalizable的接口类

序列化:

import java.io.*;

class User implements java.io.Serializable{  //创建一个user类,并实现Serializable接口
    public String name;
    public int age;
    public void method01(){
        System.out.println("名字"+name+"年龄:"+"age");
    }
}

public class demo01 {
    public static void main(String[] args) {
        User a = new User();    //静态方法中不能引用非静态变量
        a.name = "小明";
        a.age = 19;
        try {
            FileOutputStream file01 = new FileOutputStream("C:\\Users\\0x_end\\Desktop\\1.txt");//输出到本地文件的路径
            ObjectOutputStream out = new ObjectOutputStream(file01);    //序列化对象
            out.writeObject(a);
            out.close();    //关闭流对象
            file01.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
会在桌面上生成一个1.txt的文件
ObjectOutputStream.writeObject()方法生成的是一个二进制文件,所以我们用记事本打开会出现乱码
我们可以用一个二进制/十六进制的编辑器来打开文件

反序列化:

import java.io.*;

class User implements java.io.Serializable{  //创建一个user类,并实现Serializable接口
    public String name;
    public int age;
    public void method01(){
        System.out.println("名字"+name+"年龄:"+"age");
    }
}

public class demo01 {
    public static void main(String[] args) {
        User a = new User();
        try {
            FileInputStream file02 = new FileInputStream("C:\\Users\\0x_end\\Desktop\\1.txt");	//文件路径
            ObjectInputStream out2 = new ObjectInputStream(file02);	//反序列化
            a = (User) out2.readObject();	//读取文件
            file02.close();	//关闭流对象
            out2.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(a.name);
        System.out.println(a.age);
    }
}
成功输出

URLDNS分析

payload

先看URLDNS的payload,使用getObject()获取我们传入的参数URL
URLStreamHandler handler = new SilentURLStreamHandler();    //创建一个URLStreamHandler
HashMap ht = new HashMap();    //将包含URL的HashMap
按照我的理解可能是,创建一个HashMap的对象ht,用于存放URL经过HashMap操作后的数据,也是最后返回的值

URL u = new URL(null, url, handler); // URL要用作键
这一句就好理解了,HashMap存储的内容是键值对,创建一个URL的对象u,作为KEY(键)

ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
添加键值对,u为key,url为value(值)

Reflections.setFieldValue(u, "hashCode", -1);
修改了"hashCode"字段的值为-1

利用链

在注释中我们可以看到利用链
 /*   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()
 */

readObject方法

文件路径:..\..\JAVA\lib\src\java.base\java\util\HashMap.java
根据注释我进入到HashMap中的readObject方法,可以看到在49行,调用了putval方法,并对key进行了hash计算。
    /**
     * Reconstitutes this map from a stream (that is, deserializes it).
     * @param s the stream
     * @throws ClassNotFoundException if the class of a serialized object
     *         could not be found
     * @throws IOException if an I/O error occurs
     */
    @java.io.Serial
    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);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
            @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);
            }
        }
    }

hash方法

文件路径:..\..\JAVA\lib\src\java.base\java\util\HashMap.java
然后跟进到hash方法,如果key的值等于null返回0,否则就返回key.hashCode与(h>>>16)异或后的结果
h>>>16:无符号右移16位
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

hashCode方法

文件路径:..\..\JAVA\lib\src\java.base\java\net\URL.java
在URLDNS.java中可以看到我们传入的key其实是一个URL对象,所以我们可以看URL.java中的hashCode方法,如果hashCode不等于-1,就返回hashCode,否则执行handler.hashCode(this)
    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }
handler是定义的URLStreamHandler字段,跟进到URLStreamHandler.java中,这里简单来说就是解析URL的各个部分,贴一段网上的简单介绍
“一般而言,URL 的格式是: protocol://[authority]hostname:port/resource?queryString 。 URL 类能够解析出 protocol、 hostname 、 port 等信息。 Protocol 决定了交互规范,通用的协议,比如 HTTP 、 File 、 FTP 等协议, JDK 自带了默认的通讯实现。当然,自定义实现是允许的。 Hostname 和 port 一般用于 Socket 或者基于 Socket 其他协议通讯方式。Resource 即资源上下文。可能读者利用 URL ,通过指定协议( protocol )来获取指定资源的读写,比如 JDK 内置了HTTP 、 File 、 FTP 等协议的处理方法。”
然后在第18行,调用了getHostAddress方法
文件路径:..\..\JAVA\lib\src\java.base\java\net\URLStreamHandler.java
    /**
     * Provides the default hash calculation. May be overridden by handlers for
     * other protocols that have different requirements for hashCode
     * calculation.
     * @param u a URL object
     * @return an {@code int} suitable for hash table indexing
     * @since 1.3
     */
    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方法

文件路径:..\..\JAVA\lib\src\java.base\java\net\URLStreamHandler.java
跟进到getHostAddress方法,在第19行根据主机名确定主机的IP地址,这就相当于一次DNS查询
    /**
     * Get the IP address of our host. An empty host field or a DNS failure
     * will result in a null return.
     *
     * @param u a URL object
     * @return an {@code InetAddress} representing the host
     * IP address.
     * @since 1.3
     */
    protected synchronized InetAddress getHostAddress(URL u) {
        if (u.hostAddress != null)
            return u.hostAddress;

        String host = u.getHost();
        if (host == null || host.isEmpty()) {
            return null;
        } else {
            try {
                u.hostAddress = InetAddress.getByName(host);
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }
总结一下攻击链大概的流程就是:
readObject()-->putval()-->hash()-->hashCode()
URLStreamHandler-->hashCode()-->getHostAddress()-->getByName()

附:

putval方法
    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

参考文章:

https://www.cnblogs.com/ph4nt0mer/p/11994384.html
posted @ 2020-12-20 20:56  蹲在路边吃红薯  阅读(114)  评论(0编辑  收藏  举报