JAVA反序列化系列第一课:URLDNS链全解

admin 2023年10月29日23:09:54评论18 views字数 9173阅读30分34秒阅读模式

    哈喽小伙伴们,好长时间没有更新了,因为我们实验室的师傅们都比较忙,本人也刚转型去做数据安全方面,但是大家不用担心,建立实验室的初心我们会一直守护下去,那好久不见,你们都还好吧,容笔者献丑一篇关于java urldns链的详细全解,希望师傅们可以又学到新东西或者批评拷打。

    话不多说,我们开始!

    首先,还是来复习一下序列化和反序列化的概念哈:

一. Java反序列化前置知识


序列化是指将对象转化为字节流,其目的是便于对象在内存、文件、数据库或者网络之间传递。

反序列化则是序列化的逆过程,即字节流转化为对象的过程,通常是程序将内存、文件、数据库或者网络传递的字节流还原成对象。 Java 原生的API 中,序列化的过程由 ObjectOutputStream 类的 writeObject()方法实现,反序列化过程由 ObjectInputStream 类的 readObject()方法实现。将字节流还原成对象的过程都可以称作反序列化,例如,JSON 串或 XML 串还原成对象的过程也是反序列化的过程。同理,将对象转化成 JSON 串或 XML 串的过程也是序列化的过程。

Java 序列化通过 ObjectOutputStream 类的 writeObject()方法完成,能够被序列化的类必须要实现Serializable 接口或者 Externalizable 接口Serializable 接口是一个标记接口,其中不包含任何方法。Externalizable 接口是 Serializable 子类,其中包含writeExternal()readExternal()方法,readObject()方法分别在序列化和反序列化的时候自动调用。开发者可以在这两个方法中添加一些操作,以便在反序列化和序列化的过程中完成一些特殊的功能。

JAVA反序列化系列第一课:URLDNS链全解

原文链接:https://blog.csdn.net/weixin_49248030/article/details/127665350

(非常感谢这位师傅的基础知识博客)所以我们可以得出下列的结论:

Java原生链序列化:利用Java.io.ObjectInputStream对象输出流的writerObject方法实现Serializable接口,将对象转化成字节序列。

Java原生链反序列化:利用Java.io.ObjectOutputStream对象输入流的readObject方法实现将字节序列转化成对象。

    接下来让我们上源码来感受一下反序列化攻击是怎样实现的:

ackage ysoserial.SerialAttackDemo;

import java.io.*;

public class DarkerSerialAttackDemo implements Serializable {

    //反序列化
 private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException{
        in.defaultReadObject();
        Runtime.getRuntime().exec("calc");
        System.out.println("You're hacked__________Darker");
    }

    public static void main(String[] args){
        DarkerSerialAttackDemo d = new DarkerSerialAttackDemo();
        Bug.serialOnWriteOption(d);
        Bug.DeserialOnReadOption();
        d.toString();
    }
}
class Bug{
    //序列化操作
    public static void serialOnWriteOption(Object obj){
        try{
            //new FileOutputStream()是创建一个文件输出对象,指定了要创建或者写入数据的文件,然后FileOutputStream 对象被传递给 ObjectOutputStream 构造函数,用于创建一个将对象序列化写入文件的输出流
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Darker.txt"));

            //写入
            os.writeObject(obj);

            //强制将缓冲区中的数据立即写入到目标位置,而不等待缓冲区填满或其他条件的发生。这可以确保在需要时,数据的写入是及时的。
            os.flush();
            os.close();

        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    //反序列化操作
    public static void DeserialOnReadOption(){
        try{
            //new FileInputStream()是创建一个文件输入对象,指定了要读取的文件,然后FileInputStream 对象被传递给 ObjectInputStream 构造函数,用于创建一个读取序列化对象的输入流
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("Darker.txt"));

            //读取字节流
            Object object = is.readObject();

            is.close();

        }catch (IOException e){
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

JAVA反序列化系列第一课:URLDNS链全解

    可以看到命令执行,简单讲解一下,Java 反序列化通过 ObjectInputStream 类的 readObject()方法实现。

(案例源码中使用ObjectInputStream)

JAVA反序列化系列第一课:URLDNS链全解

    而在反序列化的过程中,一个字节流将按照二进制结构被序列化成一个对象。

    当开发者重写readObject 方法或 readExternal 方法时,其中如果隐藏有一些危险的操作且未对正在进行序列化的字节流进行充分的检测时,则会成为反序列化漏洞的触发点

(案例源码中传入的对象有重写的readObject方法)

JAVA反序列化系列第一课:URLDNS链全解

    简单介绍完JAVA反序列化造成攻击的本质和原理,接下来先介绍一下ysoserial环境搭建

二. 分析环境搭建


    这里我们使用老朋友,JetBrains的IDEA,破解版麻烦师傅们自行查找了哈

    然后使用ysoserial这个集成化payload生成项目来进行:

    https%3A//github.com/frohoff/ysoserial

    在IDEA中记得用以下版本的SDK:

JAVA反序列化系列第一课:URLDNS链全解

    如果没有的小伙伴可以按如下方式操作:

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

    这样即可。

. URLDNS攻击链介绍与分析调试


    URLDNS是一个在 Java 反序列化攻击中常用的反序列化链(deserialization gadget)之一。它利用了 Java 的 URL 类在反序列化时会发起 DNS 请求的特性。

    当一个包含URLDNS 的序列化数据被反序列化时,它会尝试解析指定的 URL,并发起 DNS 请求。攻击者可以构造特定的 URL,使得 DNS 请求发送到攻击者控制的恶意 DNS 服务器上。这样,攻击者就可以在 DNS 服务器上获取相关的信息,例如获取目标系统的 IP 地址或执行其他恶意操作。

    由于这种攻击利用了 Java 反序列化漏洞,因此它仅在存在反序列化漏洞的系统上才会成功

这里要注意两点:

1、该序列化漏洞与JDK版本无关,也与第三方依赖库无关,攻击链上的函数均依赖于JAVA内置类

2、URLDNS这条反序列化链只能发起DNS请求,因为它利用了 Java 的 URL 类的特性和相关的执行函数。

    在 Java 中,java.net.URL类是用于处理统一资源定位符(URL)的类。当对一个 URL 类型的对象进行反序列化时,Java 反序列化机制会调用 URL 类的构造函数,其中会执行一些初始化操作。在执行这些初始化操作期间,URL 类会尝试解析指定的 URL。

 在URL 类的构造函数中,会调用 URLStreamHandler 的 openConnection() 方法,用于打开与 URL 相关的连接。而 URLStreamHandler 的默认实现中,会调用 InetAddress.getAllByName() 方法来解析主机名(hostname),这个方法会触发 DNS 查询。

    因此,当URL 类在反序列化过程中被实例化时,它会尝试解析 URL 中的主机名,并触发 DNS 查询。攻击者可以构造一个恶意的 URL,使得 DNS 请求发送到攻击者控制的恶意 DNS 服务器上,进而进行信息收集或其他恶意行为。

    这就是为什么URLDNS 反序列化链能够发起 DNS 请求的原因,同时也可以作为是否有反序列化漏洞的验证。

    那这里有现成的payload环境,我们就开始分析调试:

    首先来到payload下的URLDNS文件:

JAVA反序列化系列第一课:URLDNS链全解

    在主函数中打断点然后开始调试:

JAVA反序列化系列第一课:URLDNS链全解

报错了:

JAVA反序列化系列第一课:URLDNS链全解

    报错了没关系啊,我们来看一下报错信息,这边是有一句语句被print打印出来了,那么说明我们的程序是执行到某个位置然后停了下来,ctrl+shift+f全局搜索一下这句:“generating payload object(s) for command”

JAVA反序列化系列第一课:URLDNS链全解

    可以看到是在PayloadRunner类啊,然后我们进去看一下,找到了确实是在此处,然后继续结合报错当中的提示:Exception in thread "main" java.net.MalformedURLException: no protocol: calc.exe

JAVA反序列化系列第一课:URLDNS链全解

    这个我去查了一下关于ysoserial的文章,在PayloadRunner下面的getFirstExistingFile中我们需要把return中的值改为我们dnslog的临时地址,因为这个报错的意思是说没有calc.exe这么一个协议,然后这个ysoerial的运行结构是由PayloadRunner去统一调用运行其他各部分的paylaod,所以针对urldns这个链是否要单独修改笔者这边在后面的其他反序列化中会仔细研究一下。

    好,这里就先替换一下一些在线的dnslog平台生成的子域名,然后我们就可以发现不会报错了,这也证明了报错的原因,所以遇到报错的时候不要怕,认真仔细阅读报错的含义然后增加自己的排查思路:

JAVA反序列化系列第一课:URLDNS链全解

    试着先运行一下程序:

JAVA反序列化系列第一课:URLDNS链全解

    看到了,确实有dns请求过来:

JAVA反序列化系列第一课:URLDNS链全解

    然后这边我们开始debug,选择单步步入哈,调试这块基本功我就不多讲了:

JAVA反序列化系列第一课:URLDNS链全解

    这里首先是最终会返回一个字节数组,然后这个数组会传入到一个反序列化处理方法中,那这样就说明第一句当中的回调代码里面会做一个序列化的操作,记得我们之前讲的吗,序列化就是将对象转换成字节流:

JAVA反序列化系列第一课:URLDNS链全解

    然后这里需要注意的是,我们先来看他的代码写法:

byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){
   public byte[] call() throws Exception {
···
}});

    这里先是创建了一个Callable<T>的匿名对象,这是一个带泛型的接口,这一点可以单步进入calWrapped方法中查看验证:

JAVA反序列化系列第一课:URLDNS链全解

    这里也简单介绍一下Callable<T>接口:

    例如,下面是一个实现了Callable<Integer> 接口的示例:

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

@Override

public Integer call() throws Exception {

// 实现逻辑

// 返回一个整数作为结果

return 42;

}

}

    在上述示例中,MyCallable类实现了 Callable<Integer> 接口,并覆盖了 call() 方法。在 call() 方法中,你可以编写实际的任务逻辑,并返回一个整数作为结果。

    通过使用泛型参数T,Callable<T> 可以支持返回不同类型的结果,例如 Integer、String、List 等。这样可以提高代码的灵活性和可重用性。

    所以既然是一个匿名对象接口,那么代码中接下去的 public byte[] call()就是(返回字节数组)对应我(new Callable<byte[]>()中的<byte[]>。

    继续单步进入:

JAVA反序列化系列第一课:URLDNS链全解

    单步运行到这一步就会返回,原因是我之前说了,传入了一个匿名对象,然后调用其中的call()方法

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

    然后我们来看一下哪里做了序列化操作:

    这句话是用到了一个三目运算符哈,判断命令行传参是否为空,为空的话就使用默认的payload,这个paylaod由getDefaultTestCmd()方法提供。

JAVA反序列化系列第一课:URLDNS链全解

    单步进入之后可以发现最终拿到这个paylaod的dns地址

JAVA反序列化系列第一课:URLDNS链全解

    这两个方法主要是这个ysoserial工具开发者提供的默认payload生成,没什么好研究的,自己走一遍调试就清楚了。

    然后我们继续单步出来,这句话的clazz对象就是URLDNS类对象,单步进去的话,会过一下初始化方法然后出来,没什么好说的

JAVA反序列化系列第一课:URLDNS链全解

    然后继续单步到这一步我们可以看到之前设置的dnslog的地址已经被赋值了,然后通过这句话传递给一个object对象:

JAVA反序列化系列第一课:URLDNS链全解

    然后在这句话单步进入到URLDNS的getObject函数,可以看到其中声明了HashMap对象和URL对象,而且传入的参数的值也可以看得到,并进行了一个put哈希绑定,也就是k-v绑定:

JAVA反序列化系列第一课:URLDNS链全解

    然后单步掠过出来,到下图为止,payload对象就已经生成好了:

JAVA反序列化系列第一课:URLDNS链全解

    然后就是序列化:

JAVA反序列化系列第一课:URLDNS链全解

至此,Ysoserial的攻击链的序列化代码已查看完毕,接下来看原生的反序列化链子

四. URLDNS原生反序列化利用链分析调试


    由之前的操作一直单步步过就好,直到这一句反序列化入口处打上断点,可以看到我们之前序列化好的payload已经正准备好传进来:

JAVA反序列化系列第一课:URLDNS链全解

    单步进入后可以看到这里将字节流变成一个输入流对象然后传入到deserialize()方法中:

JAVA反序列化系列第一课:URLDNS链全解

    这里我们继续单步进入,发现这里又被转换成了一次输入流对象然后传入一个叫readObject的方法中,注意这里的objIn是原生java的ObjectInputStream类对象噢:

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

    然后这么一直单步走下去···就结束了!?,然后结果运行,欸!???好像哪里有点不对劲,怎么反序列化了一下过了一个ObjectInputStream原生内部方法然后就没了,怎么没看到一点我这个序列化被反序列化的过程呢。

    好,注意哈,现在我们回到之前调试到URLDNS这里:

JAVA反序列化系列第一课:URLDNS链全解

    记不记得这里我们new了一个HashMap对象,而反序列化攻击当中实现重要攻击的步骤是什么,是不是就是我们重写了一个readObject方法而已啊,那其实链子的思路就是专门找这种重写过readObject方法的类,很巧的是,HashMap类就重写了readObject方法。

    我们接下去点红色的force step into进入,一开始是一个ClassLoader,没关系,步出之后再红色步入一下就是我们的HashMap:

JAVA反序列化系列第一课:URLDNS链全解

    Ctrl+f局部搜索readObject:

JAVA反序列化系列第一课:URLDNS链全解

    然后我们在此函数入口处打一个断点:

JAVA反序列化系列第一课:URLDNS链全解

    然后重新开启debug到这一步,不用担心断点会消失,那为什么要运行到这一步呢,因为这里不是调用了readObject方法吗,在 Java 的反序列化过程中,如果一个类重写了readObject 方法,那么在反序列化该类的对象时,会调用重写的 readObject 方法而不是默认的反序列化逻辑。

JAVA反序列化系列第一课:URLDNS链全解

    因此单步进入之后会去到打了断点的HashMap中的readObject方法中:

JAVA反序列化系列第一课:URLDNS链全解

    至此,真相大白,然后理清楚逻辑之后,我们接着单步调试去看,然后到这步之前的代码都是在反序列化 HashMap 对象时,恢复其内部状态,全部单步步过随便看一下就好了:

JAVA反序列化系列第一课:URLDNS链全解

    一直步到这里:

JAVA反序列化系列第一课:URLDNS链全解

    我们之前设置的dns地址是不是出现了,然后这里讲一下下面一句当中的putVal函数,简而言之,putVal()方法是 HashMap 中负责将键值对插入到哈希表中的核心方法。而hash(key)则是通过调用 hash() 方法计算键 key 的哈希值,确定键值对在哈希表中的位置。

    这里选择单步进入hash函数,使用左右键选择:

JAVA反序列化系列第一课:URLDNS链全解

    进入hash函数,和之前说的一样,就是对key进行一个计算:

JAVA反序列化系列第一课:URLDNS链全解

    然后根据我们之前传入的payload,我们的dns url是一个url类对象,因此可以跟进看一下这边调用的这个hashcode方法:

JAVA反序列化系列第一课:URLDNS链全解

    当hashcode等于-1时,执行handler.hashCode并把自己即URL对象传入,这里的hashcode默认值就是-1,我们可以继续单步往下走看看,进入到了URLStreamHandler下的hashCode方法,同时这里的u就是我们写入的dns地址:

JAVA反序列化系列第一课:URLDNS链全解

    然后我们就可以发现这个方法中有一个getHostAddress方法,这个方法会对传入的url地址进行解析:

JAVA反序列化系列第一课:URLDNS链全解

    这里先留个时间证明还没有请求:

JAVA反序列化系列第一课:URLDNS链全解

    然后我们继续单步调试,进入不同参数的getHostAddress方法:

JAVA反序列化系列第一课:URLDNS链全解

    然后步过,直到InetAddress.getByName方法,完成解析:

JAVA反序列化系列第一课:URLDNS链全解

    尴尬,写的时候太久了,子域名时间到期了,我这边重新补个图验证:

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

    OK,到此为止,dnsurl利用链分析完毕,为什么我一定要给验证截图呢,就是想告诉大家,计算机是一个很真实很严谨的东西,同样作为一个优秀的黑客也好,程序员也好,本质都是一样的。

    然后接下来一直步过会回退到后面的反序列化之后的代码片段中,然后程序运行结束。

JAVA反序列化系列第一课:URLDNS链全解

JAVA反序列化系列第一课:URLDNS链全解

    所以对于这个ysoserial项目而言整个调用链为

1)Deserializer.deserialize.objIn(ObjectInputStream).readObject()->有重载过readObject->HashMap

2)HashMap->readObject

3)HashMap->hash

4)URL->hashCode

5)URLStreamHandler->hashCode

6)URLStreamHandler->getHostAddress->getbyname产生解析

五. 使用ysoserial生成payload以及实战运用

    来到我们的Serialize类下的serialize方法中添加下列代码:

FileOutputStream fileOutputStream = new FileOutputStream("darkerPayload.ser");
ObjectOutputStream objectoutputStream=new ObjectOutputStream(fileOutputStream);
objectoutputStream.writeObject(obj);
fileOutputStream.flush();
fileOutputStream.close();
objectoutputStream.flush();
objectoutputStream.close();

JAVA反序列化系列第一课:URLDNS链全解

    然后在实战当中,有以下几种业务情况会出现反序列化的运用:

1、远程调用接口:在分布式系统中,远程调用接口允许不同的服务或应用程序之间进行通信和交互。这些接口可能使用序列化和反序列化来传输和还原对象,以便在不同的节点上进行方法调用和数据传递。

2、数据持久化接口:当需要将对象存储到持久化介质(如数据库、文件系统等)中时,可以使用序列化和反序列化。通过将对象序列化为字节流,可以将其保存到文件或数据库中,然后通过反序列化将其还原为对象。

3、缓存接口:在缓存系统中,对象可以被序列化并存储在缓存中,以便在需要时进行快速访问。当需要从缓存中获取对象时,可以使用反序列化将存储的字节流转换为原始对象。

4、消息队列接口:在消息队列系统中,消息的发送和接收可能涉及对象的序列化和反序列化。当消息被发送到队列时,可以将对象序列化为消息的内容,并在接收端使用反序列化还原为原始对象。

    碰到这些接口,直接梭哈就对了,因为我们这个payload用的是java原生的类来进行一个序列化操作,只要他后端接口没有对序列化后的对象进行安全检查,那么在序列化payload对象之后,就会走HashMap的调用链去触发dns请求。

六. Dnslog 平台推荐以及私有dns平台搭建


好用的dnslog在线平台_dnslog平台_SuperherRo的博客-CSDN博客

GitHub - BugScanTeam/DNSLog: DNSLog 是一款监控 DNS 解析记录和 HTTP 访问记录的工具。

JAVA反序列化系列第一课:URLDNS链全解

好啦,感谢一直坚持收看到最后, 那这次也拜托持转发收藏扩散啦,darker我对这个做不断更新


    那HexaGoners六边形者



原文始发于微信公众号(HexaGoners):JAVA反序列化系列第一课:URLDNS链全解

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月29日23:09:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA反序列化系列第一课:URLDNS链全解http://cn-sec.com/archives/2157349.html

发表评论

匿名网友 填写信息