JAVA安全学习笔记--URLDNS链的复现(反射+反序列化)

URLDNS链(反射+反序列化)

介绍:URLDNS其实是ysoserial 中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。

因为它最终利用成功只不过是反序列化的时候触发DNS请求,它并不能实现命令执行。

虽然这个链子实际上不能“利用”,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤。

  • 使⽤ Java 内置的类构造,对第三⽅库没有依赖。
  • 完整的展示了反序列化漏洞的利用过程(即使只是触发一个DNS请求)

可以去ysoserial里看看是怎么构造URLDNS链的:

image-20250906172913704

可以发现利用链非常的简单:

Gadget Chain:
    HashMap.readObject()
        HashMap.putVal()
            HashMap.hash()
                URL.hashCode()

我们来手动复现一下:

我们ctrl+左键进入到URL类里看看:

在URL类的方法中,我们很容易发现一个openConnection()方法

image-20250906175301103

但是跟进以后可以发现并不是常用的方法,所以我们淘汰它。我们接着往下翻

image-20250906175509554

欸?hashCode方法我们前面分析HashMap的时候不是见过吗,这个方法是Object类里的一个方法,也就是说这个方法是一个常用方法。注意到又调用了handler里的hashCode方法,继续跟进

image-20250906175730999

发现handler里的hashCode方法调用了getHostAddress方法,继续跟进

image-20250906175900987

这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其 IP 地址,在⽹络上其实就是⼀次 DNS 查询。到这⾥就不必要再跟了。

最终目的很明显了,但我们如何让反序列化的时候,也就是readObject的时候通过Gadget Chain,最终调用gerByName方法呢?

这个时候就要找入口类了,我们前面反序列化基础的时候便提到过HashMap。在HashMap的自定义readObject方法中,会调用hash方法,然后在hash方法中调用了K-->hashCode(),这里的K就是键名,是我们可控的,我们可以通过HashMap的put方法传入。也就是说我们可以将URL作为K,通过put方法传入,然后在hash方法中调用URL.hashCode()。

那么Gadget Chain就显而易见了:

HashMap.readObject() --> HashMap.hash() --> URL.hashCode --> URLStreamHandler.hashCode --> URLStreamHandler.getHoseName() --> InetAddress->getByName()

初步复现(碰壁)

我们在SerializationTest.java 作如下修改

导入三个类

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

然后添加如下代码

HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();   
hashmap.put(new URL("DNS生成的 URL,用dnslog就可以"),1);

serialize(hashmap);

这里的URL用DNSLOG或者BP的Collaborator client生成都可以。

image-20250906182510576

点一下那个copy便可以复制链接

然后UnserializeTest.java修改

    public static void main(String[] args) throws Exception{
        unserialize("ser.bin");
    }

然后先运行SerializationTest.java,这个时候惊奇的发现,为什么我序列化的时候就发起DNS请求了?我还没进行反序列化啊喂!

image-20250906204201577

然后我们再运行一下UnserializeTest.java,发现这个时候又不发起DNS请求了。

这不对吧喂!怎么跟我们设想的反过来了?经过测试发现原来是put方法在作祟。我们看看put方法是怎么执行的。

image-20250906204833139

我们可以发现put方法竟然也调用了hash方法,继续跟进

image-20250906204905636

注意到这里也调用了Object key的hashCode()方法,注意这里不要直接ctrl+左键跟进,这样跟进的是Object类的hashCode方法,不是URL的hashCode方法。所以我们从URL类里找到hashCode跟进。

image-20250906205709437

我们再仔细看看hashCode的代码实现,我们发现使用hashCode的值为-1的时候才会跟进到handler的hashCode方法最终发起DNS请求。我们往上翻

image-20250906205916792

可以发现hashCode的初始值便是-1。

这就导致我们运行SerializationTest.java便已经触发了DNS请求(其实执行了put就触发了),触发了以后hashCode值被修改了肯定不为-1了,这时候反序列化便不满足跟进到handler.hashCode里了。

那么怎么样才能让其按照我们预想的方式进行呢(反序列化时触发DNS)。

这个时候便要用到反射的知识了。

改进复现(利用反射)

有关反射的部分可以自己去看我前面的内容。

现在我们知道了,只有当hashCode值为-1的时候才会跟进到handler.hashCode()并执行后面的操作。我们又不想让执行put方法的时候也发起DNS请求影响我们判断。这个时候我们便可以通过反射来改变一个已有对象的属性。

使得

hashCode!=-1
执行put(。。。)
hashCode=-1

利用getClass()获取class对象,然后利用getDeclaredField获取hashCode字段,然后设置作用域后修改hashCode的值便是反射的执行过程。

POC:

// 这里不要发起请求  
URL url = new URL("http://aaojd7ec6yxash8gdi23vef7hynobd.burpcollaborator.net");  
Class c = url.getClass();  
Field hashcodefile = c.getDeclaredField("hashCode");  
hashcodefile.setAccessible(true);  
hashcodefile.set(url,666);  
hashmap.put(url,1);  
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性  
hashcodefile.set(url,-1);  
serialize(hashmap);

再重新运行序列化和反序列化文件,可以这次正常了只有反序列化后才会触发DNS请求。

image-20250906211340803

至此URLDNS链就利用完成了,是不是觉得这也没造成啥危害了,不就是触发了一次DNS请求吗?

确实这个链子没啥危害,但是正因为它简单,用它来测试是否存在反序列化漏洞再合适不过了。

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注