Java安全之URLDNS链
2022-03-24 09:58:43 # Java安全

前言

URLDNS链是ysoserial里面的一条简单的利用链,但URLDNS的利用效果是只能触发一次DNS请求,而不能去执行命令,比较适用于漏洞验证这一块。而且URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。

反序列化漏洞成因

序列化指把Java对象转换为字节序列的过程,反序列化就是打开字节流并重构对象,那如果即将被反序列化的数据是特殊构造的,就可以产生非预期的对象,从而导致任意代码执行。

Java中间件通常通过网络接收客户端发送的序列化数据,而在服务端对序列化数据进行反序列化时,会调用被序列化对象的readObject( )方法。而在Java中如果重写了某个类的方法,就会优先调用经过修改后的方法。如果某个对象重写了readObject( )方法,且在方法中能够执行任意代码,那服务端在进行反序列时,也会执行相应代码。如果反序列化的数据是可控的情况下,那么我们就可以从某个输入点,输入恶意代码,再去查找在哪个点,我们的输入会被一层一层的带去到我们的触发点去,而这一步叫做寻找利用链的步骤。

动态调试ysoserial

ysoserial jar : https://jitpack.io/com/github/frohoff/ysoserial/master-30099844c6-1/ysoserial-master-30099844c6-1.jar

ysoserial 源码:https://github.com/frohoff/ysoserial

下载源码,导入到IDEA中,刷新maven,下载好依赖,查看pom.xml,搜索mainClass可以找到入口类

image-20220324121703214

进入到GeneratePayload,配置启动参数

image-20220324121752642

再次运行就可以了

image-20220324145146854

下面分析下ysoserial是怎么生成序列化数据的

随便下个断点进入Utils.getPayloadClass方法中,代码就简单利用反射获取到了URLDNS的Class对象

image-20220324145759828

往下走就进入getObject()方法中

image-20220324150429427

getObject()方法中,创建了一个hashmap,并将URL对象当做map的key值,value值随意,最后还通过反射修改了hashCode的值为-1,这里修改的原因后面分析。

image-20220324150611827

继续往下,就是调用serialize()方法

image-20220324150953288

进入,可以看到这里就是将上一步返回的HashMap进行序列化输出至控制台

image-20220324151038792

URLDNS链分析

打开ysoserial\payloads\URLDNS.java,在源码的注释中可以看到对调用链的描述,翻译过来就是

Java URL 类在其 equals 和 hashCode 方法上有一个有趣的属性。作为副作用,URL 类将在比较期间进行 DNS 查找(equals 或 hashCode)。作为反序列化的一部分,HashMap 在它反序列化的每个键上调用 hashCode,因此使用 Java URL 对象作为序列化键可以触发 DNS 查找

image-20220324122317730

调用链如下

1
2
3
4
5
*   Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()

具体的调用过程,我们下断点调试看看,先生成序列化数据

image-20220324154308028

简单写个反序列化入口

image-20220324154621771

触发DNS查询

image-20220324154723458

下面开始分析,根据上述的Gadget Chain,可见触发点是在HashMap.readObject(),来到hashmapreadobject()方法,然后一直F8,根据Gadget Chain发现使用了putVal()方法,但这不是重点,重点是会调用hash方法

image-20220324163858671

这里使用了hash方法对key的值进行了处理,我们来跟踪一下hash这个方法看看他具体的实现

image-20220324163943772

如果key不是null就会调用key.hashCode()方法,跟进hashCode()方法,这里调用的是URL类中的hashCode()方法

image-20220324164055912

hashCode值不为-1时就直接return,就不会触发hashCode()方法,也就不会触发接下来的DNS解析,这里hashCode值默认为 -1,所以会执行 handler.hashCode(this),URLDNS链中也通过反射将hashCode的值设置为-1,也就是URLDNS的getObject

()方法中设置hashCode为-1的原因了。

image-20220324150611827

看一下handler,是URLStreamHandler类(也是我们传入的handler),就是上面URLStreamHandler对象

image-20220324164559978

也就是说这里调用的是URLStreamHandler.hashCode()方法,跟进hashCode()方法,发现这里调用了getHostAddress()方法,见名思意就知道这里是做DNS查询

image-20220324165008096

跟进getHostAddress()方法,发现会调用getHost()方法发起DNS请求

image-20220324165204708

到此就结束了,调用链如下

image-20220324165333365

思考

分析过程中,发现HashMap.put()方法中也调用了hash()方法,然后去进行hashCode计算等

image-20220325093950339

那么就是说,在put操作的时候,也会触发对应的DNS解析,编写测试代码

image-20220325094032461

成功解析DNS

image-20220325094056984

但是,ysoserial在生成序列化数据的时候却并没有收到DNS解析,原因就在于继承抽象类URLStreamHandlerSilentURLStreamHandler类中,重写了openConnection()getHostAddress()

image-20220325094453830

因此在调用 put 方法的时候不会触发DNS 查询,下面编写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UrlDNStest2 {
public static void main(String[] args) throws MalformedURLException {
URLStreamHandler urlStreamHandler = new URLStreamHandler(){
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u){
return null;
}
};
HashMap map = new HashMap();
URL url = new URL(null, "http://p8v57z.dnslog.cn", urlStreamHandler);
map.put(url, 2);
}
}

image-20220325095447625

根据之前分析之所以会产生后面的DNS解析的一个关键是URL.hashCode的值是-1,那么要想让put()方法不产生DNS解析,可以在put方法之前设置hashCode为一个不为-1的值

image-20220325105209379

image-20220325105151582

那为什么反序列化之后又可以进行DNS解析呢,这里查看URL类的源码,可以看到handler属性被设置为了transient,在反射的学习中可以知道,被设置了transient的是无法被序列化的,所以序列化的时候没有DNS解析。

image-20220325102243557

总结

整个调用链梳理下就是

1
HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode().getHostAddress() -> URLStreamHandler.getHostAddress().InetAddress.getByName()

可能存在反序列化漏洞的形式。

1
2
3
4
1.入口类的readObject直接调用危险方法。
2.入口类参数中包含可控类,该类有危险方法,readObject时调用。
3.入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用。
比如类型定义为Object,调用equals/hashCode/toString方法等。重点相同类型,同名函数。

参考

Java安全之URLDNS链 - nice_0e3 - 博客园 (cnblogs.com)

https://blog.csdn.net/solitudi/article/details/117235572