• yeoserial之URLDNS调用链分析


    环境准备

    package org.URLDns;
    
    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    
    
    public class URLDNS {
        public static void main(String[] args) throws Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\Users\86183\Desktop\out.bin"));
            ois.readObject();
        }
    }
    
    

    利用链分析

    通过上面一篇文章,搭建后,然后我们对最简单的URLDNS链进行分析

    触发反序列化的⽅法是readObject,那么,我们可以直奔 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);
    
                // Check Map.Entry[].class since it's the nearest public type to
                // what we're actually creating.
                SharedSecrets.getJavaOISAccess().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);
                }
            }
        }
    

    HashMap:键唯一,键值对存取无序, 由哈希表保证键唯一
    在最后一行中,我们可以看到将HashMap的键名计算了hash,以此保证键唯一:

    putVal(hash(key), key, value, false, false);
    

    以至于我们为什么关注这个点?

    因为ysoserial提到了hashCode计算操作时触发了DNS请求

    我们可以看到这边调用链,以及代码,这边readObject调用了hash()
    我们跟进去看

    发现hash()方法又调用了hashcode()方法,由于key是java.net.URL类型

    我们继续跟进hashCode()方法,因为是该类型,所以进入的hashcode()方法不一样(可能是跟我女朋友打电话,搞得我没法专心,断点下错了还是啥)

    hashcode就是等于-1,所以进入下面一行,handler 是 URLStreamHandler 对象

    之后我们继续跟进hashcode()方法

    此处调用了 getHostAddress,我们再一次的f7跟进

    这里的InetAddress.getByName,在给定主机名的情况下确定主机的IP地址,意思就是发起一次请求
    如果还有不懂的,可以看看这篇文章 Java学习之网路编程

    所以,⾄此,整个 URLDNS 的Gadget其实清晰⼜简单:

    1. HashMap->readObject()
    2. HashMap->hash()
    3. URL->hashCode()
    4. URLStreamHandler->hashCode()
    5. URLStreamHandler->getHostAddress()
    6. InetAddress->getByName()

    总的来说:
    在序列化 HashMap 类的对象时, 为了减小序列化后的大小, 并没有将整个哈希表保存进去, 而是仅仅保存了所有内部存储的 key 和 value. 所以在反序列化时, 需要重新计算所有 key 的 hash, 然后与 value 一起放入哈希表中. 而恰好, URL 这个对象计算 hash 的过程中用了 getHostAddress 查询了 URL 的主机地址, 自然需要发出 DNS 请求

    要构造这个Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap
    中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算
    其 hashCode ,才能触发到后⾯的DNS请求,否则不会调⽤ URL->hashCode()

    参考文章

    p牛的java漫谈
    百度上的一些文章

  • 相关阅读:
    Quartz.Net系列(二):介绍、简单使用、对比Windows计划任务
    Quartz.Net系列(一):Windows任务计划程序
    Linux下swap到底有没有必要使用
    Linux服务器有大量的TIME_WAIT状态
    HTTP请求头中的X-Forwarded-For介绍
    Keepalived实现服务高可用
    Gitlab常规操作
    Git 常用命令
    HTTP常见状态码
    《Docker从入门到跑路》之多阶段构建
  • 原文地址:https://www.cnblogs.com/0x7e/p/14313455.html
Copyright © 2020-2023  润新知