URLDNS 利用链分析

发布于 2021-11-28  33 次阅读


什么是ysoserial

反序列化漏洞在各个语⾔⾥本不是⼀个新鲜的名词,但2015年Gabriel Lawrence (@gebl)和ChrisFrohoffff (@frohoffff)在AppSecCali上提出了利⽤Apache Commons Collections来构造命令执⾏的利⽤链,并在年底因为对Weblogic、JBoss、Jenkins等著名应⽤的利⽤,⼀⽯激起千层浪,彻底打开了⼀⽚Java安全的蓝海。⽽ysoserial就是两位原作者在此议题中释出的⼀个⼯具,它可以让⽤户根据⾃⼰选择的利⽤链,⽣成反序列化利⽤数据,通过将这些数据发送给⽬标,从⽽执⾏⽤户预先定义的命令

什么是利用链

利⽤链也叫“gadget chains”,我们通常称为gadget。如果你学过PHP反序列化漏洞,那么就可以将gadget理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在PHP⾥可能是 __desctruct 到 eval ;如果你没学过其他语⾔的反序列化漏洞,那么gadget就是⼀种⽣成POC的⽅法罢了。

什么是URLDNS

URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时

使⽤:

  • 使⽤Java内置的类构造,对第三⽅库没有依赖
  • 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

使用Yso生成URLDNS

DNSLog获取一个DNS解析地址,后续接受DNS请求。

image-20211126115342363

使用Yso生成利用链,并导入到husins.txt

image-20211126115800485

编写反序列化入口,触发利用链:

  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\漏洞扫描\\husins.txt"));
 Object test = ois.readObject();
 System.out.println(test);
image-20211126115928931

从源码层面上分析利用链

根据上面操作,我们不难看出,yso生成序列化字符串之后,在我们编写的反序列化入口文件中会读取这个序列化的值,然后调用这个对象的readObject()方法。

来到yso中的URLDNS处,核心代码如下:

image-20211126132830927

根据核心代码,我们不难发现我们得到的payload中生成的序列化的值,其实就是返回值ht

ht来自于实例化HashMap(),因此发序列化入口调用的readObject其实就是HashMapreadObject

因此查看Hashmap()->readObject

     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
 ​
         ObjectInputStream.GetField fields = s.readFields();
 ​
         // Read loadFactor (ignore threshold)
         float lf = fields.get("loadFactor", 0.75f);
         if (lf <= 0 || Float.isNaN(lf))
             throw new InvalidObjectException("Illegal load factor: " + lf);
 ​
         lf = Math.min(Math.max(0.25f, lf), 4.0f);
         HashMap.UnsafeHolder.putLoadFactor(this, lf);
 ​
         reinitialize();
 ​
         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) {
             // use defaults
        } else if (mappings > 0) {
             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);
            }
        }
    }

这里值得关注的是,在46行调用putVal(),并且参数里调用了 HashMaphash方法

进一步跟踪hash():

image-20211126134924224

我们发现,这里调用了Key的hashcode方法。因为Key的类型不通,他的hashcode是不一样的,因此我们分析一下key。根据上面readObject()方法中的41-45行,我们可以发现,key-value是在序列化字符串中直接读取出来的,因此我们现在回到,URLDNS上来观察在序列化之前做了什么操作。

image-20211126140516686
image-20211126140529803

根据上面两幅图我们可以发现key就是实例化的URL类的对象,所以调用的hashcode()URL类的,查看该hashcode()方法

image-20211126140821842

发现调用handlerhashCode,继续跟进hashCode()方法:

image-20211126141140954

生成主机部分调用getHostAddress,继续跟进:

image-20211126142222261

这里调用了getByName()继续深追一下,一直追到getAllByName方法:

image-20211126142614555

这个方法,就是将主机名转换为IP地址,也就是发起了一次DNS解析请求。

总结一下整个利用链:

  • HashMap.readObject()
  • HashMap.putVal()
  • HashMap.hash()
  • URL.hashCode()
  • URLStreamHandler.hashCode()
  • URLStreamHandler.getHostAddress()

URLDNS中存在ht.put,里面也存在key.hashCode()没啥没有触发DNS解析?

image-20211126143331773
image-20211126143342215

因为在序列化时,重写了URLStreamHander,将getHostAddress置空。

为啥要用反射将hashCode初始值置-1

设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算其 hashCode ,才能触发到后⾯的DNS请求,否则不会调⽤ URL->hashCode() 。