Ysoserial URLDNS链分析
Ysoserial URLDNS链分析
文章首发安全客:https://www.anquanke.com/post/id/248004
0x00 写在前面
Java提供了一种序列化的机制可以将一个Java对象进行序列化后,用一个字节序列表示,并在Java虚拟机之间或网络间传输,之后可通过readObject()
方法反序列化将字节序列反序列化还原成原先的对象。而如果对于反序列化没有做安全限制,并且反序列化点可控的话,我们可以通过一个恶意的序列化对象通过利用链,去达到执行任意代码的目的。
而我们平常复现或利用反序列化漏洞时,也经常会用到ysoserial项目去生成序列化payload,下面就先分析学习一下ysoserial中的URLDNS链。
0x01 运行ysoserial
下载ysoserial项目源码,导入IDEA
打开pom.xml文件,是个Maven项目,点击刷新拉取缺少的依赖
拉取完可能还会有些爆红的地方,但是不会影响程序运行。
下面寻找下程序的入口点,搜索mainclass即可
0x02 序列化payload生成流程
先来大致看下这个GeneratePayload的main方法。
首先进行一个判断,是否传入了两个参数,如果不是则打印帮助信息;是的话会依次分别赋值给payloadType和command变量。
之后实例化了一个需要继承ObjectPayload类的类实例化对象,跟进一下getPayloadClass方法,看看是如何实现的
在ysoserial.payloads.ObjectPayload.Utils下
可以看到会先反射一个class对象,之后获取包名和类名并加上我们之前传入的payloadType参数并将返回值赋值给GeneratePayload的payloadclass。
后续生成一个payloadclass所表示的类的新实例化对象赋值给payload,之后调用getObject方法,跟进看一下getObject方法
该方法定义于ObjectPayload接口,继续跟进
发现跳到了URLDNS,且该类实现了ObjectPayload接口。
那么上面的操作就是根据输入的参数,获取类名并定位到该类中,也就是进入到利用链的类文件中,调用该类的getObject方法生成payload并将返回值(也就是序列化payload)赋值给object对象。
URLDNS中的操作我们放到后面再分析,继续回到GeneratePayload#main方法中
之后调用serialize()方法将payload序列化并打印输出。
那么上面将大致的payload生成流程过了一遍,下面跟进去运行测试一下,生成一个URLDNS链的序列化payload。
这里没设置参数,所以可以看到出现了帮助信息,我们设置下参数
点击Edit Configurations
,如下图设置参数再重新运行,这里可以随便写个地址,或者获取一个dnslog用于测试
这次就成功运行了,下面的乱码数据就是上面分析过的,经过一系列操作生成的URLDNS序列化之后的payload字节序列
0x03 URLDNS链使用
URLDNS链的利用效果是发起一次远程请求,而不能去执行命令。基本上是用来测试是否存在反序列化漏洞的一个链,比如在一些无法回显执行命令的时候,可以通过URLDNS链去发送一个dns解析请求,如果dnslog收到了请求,就证明了存在漏洞。
下面命令行使用下URLDNS链看看效果
将生成的序列化payload保存在一个txt里,后面用一个测试demo读取文件数据再给他反序列化一下,观察dnslog请求就可以了。
java -jar ysoserial.jar URLDNS "http://ba0i6v.dnslog.cn"
java -jar ysoserial.jar URLDNS "http://ba0i6v.dnslog.cn" > URLDNS.txt
反序列化测试demo
import java.io.*;
public class URLDNSTestDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("/Users/b/Desktop/ScanTools/URLDNS.txt");
ObjectInputStream bit = new ObjectInputStream(fis);
bit.readObject();
}
}
运行demo测试是否可以成功将序列化的payload执行并在dnslog留下记录
dnslog平台已经接收到了请求,说明我们利用测试成功了。
下面我们分析一下URLDNS链执行流程是怎样的。
0x04 URLDNS链分析
URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。所以并不需要一些Java的第三方类库就可以达到目的。
打开URLDNS源码,URLDNS.java
在最上面的注释中,作者已经给出了URLDNS的Gadget chain
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
下面debug看一下是如何执行的。
触发点在put
方法,我们在put
方法处打一个断点,在Edit configurations
中设置好参数,debug mainclass文件即可
成功在断点处停下,下面我们一步一步调试分析
首先我们看下这个ht
对象的类HashMap
,可以观察到有序列化接口
那么全局搜一下readObject
方法,发现在最后调用了putVal
方法进行了一个hash计算
回到断点处跟进一下putVal
方法,参数的key
和value
都是我们输入的dnslog地址
继续跟进,看一下hash
方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里因为传入的key
不为0,会执行key.hashcode
方法,继续跟一下hashcode
方法
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
这的handler
是URLStreamHandler
的对象,这里在序列化时将hashcode
值设-1,所以直接调用了handler
的hashCode
方法并重新赋值返回了hash
,继续跟进
这里调用的是URLStreamHandler这个类的hashCode方法,此时我们传入的URL u对象其实就是我们的dnslog地址,后面通过u.getProtocol()方法获取了协议名字,也就是http
继续跟进,下面会进行一个if判断,执行语句后 h的值会加上portocal的hashCode()方法的返回值,跟一下这里调用的hashCode()方法
发现是String的hashcode()方法,这里做了一顿操作得到String的hash值并赋值给h并返回出来
继续跟进,调用了getHostAdress方法
来看一下getHostAdress是如何实现的,观察逻辑,这里会先后调用getHost()
、getByName()
两个方法,最终是通过getByName(getHost())
这样去发送的dnslog请求
到了这里,dnslog就已经可以接收到请求信息了,致此这歌利用链差不多就分析完了。
整个调用链为
HashMap.readObject() => HashMap.putVal() => HashMap.hash() => URL.hashCode() =>
URLStreamHandler.hashCode() => URLStreamHandler.hashCode().getHostAddress => URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName()
0x05 URLDNS链中的一些细节
回到URLDNS类,可以看到还有一些其他的点其实并没有去深入分析,比如下面这几段代码
...
URLStreamHandler handler = new SilentURLStreamHandler();
...
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
...
Reflections.setFieldValue(u, "hashCode", -1);
这里new的是一个SilentURLStreamHandler对象,继承自URLStreamHandler并且重写了两个方法,分别是getHostAddress和openConnection且都是空方法,比较耐人寻味。
关于重写getHostAddress方法与hashcode = -1
Java中继承子类的同名方法会覆盖父类的方法,所以说本来是要执行URLStreamHandler中的getHostAddress方法会变成执行他的子类SilentURLStreamHandler的getHostAddress方法(也就是空方法)。因为在默认情况下hashcode的值就是-1,那么在本地进行将 URL 对象 put 进 hashMap时如果hashcode值为-1就直接调用了getHostAddress方法也就会在生成序列化payload的时候在本地发送一次dnslog请求,所以ysoserial中给出对应的解决方法就是让SilentURLStreamHandler继承URLStreamHandler并重写getHostAddress为空方法,这样即使hashcode为-1也不会在本地发送请求(继承子类的同名方法会覆盖父类的方法)
Reflections.setFieldValue(u, "hashCode", -1);
最后通过反射将hashcode重新设置为-1,那么在反序列化过程就会触发dnslog请求
关于handler
在调试的时候其实就发现了一个特殊的关键字transient,不可序列化,也就是说handler最终不会在我们产生的序列化payload中,所以即便SilentURLStreamHandler重写了父类方法,也不会影响反序列化的过程
关于重写openConnection方法
这个就比较简单了,因为URLStreamHandler是一个抽象类,所以实现类必须实现父类的所有抽象方法
0x06 结尾
根本原因还是在HashMap存在反序列化点并重写了readObject方法可以接受反序列化数据,之后因为hashcode=-1,通过putVal计算hash时一步步的调用直到调用getHostAddress方法,就会发起远程请求触发dnslog。整体下来其实并不算太复杂,只是需要耐心一点一点调试,弄清楚逻辑和一些方法的作用就可以了
文章有分析的不对的地方还请师傅们指出
参考文章: