基于JAVA反序列化的URLDNS完整利用链分析

  • A+
所属分类:代码审计

00


前言


    在学习JAVA反序列化漏洞的过程中,我们常常使用DNSLOG作为漏洞验证的方法之一。通过查看dnslog.cn的域名解析记录,验证站点是否存在可利用的反序列化漏洞,同时也能作为判断目标主机是否能够出内网的依据。本文对URLDNS的利用链进行简单学习和分析。


使用环境:Jdk1.8.0_141



01


参数调试


    先简单看一下URLDNS参数需求,这里只需要提供一个URL:

基于JAVA反序列化的URLDNS完整利用链分析

    我们可以到dnslog免费生成一个临时域名(http://dnslog.cn/

基于JAVA反序列化的URLDNS完整利用链分析

    找到入口类配置执行的参数,右上角箭头指向->Edit Congurations

然后选择我们的主类GeneratePayload以及配置Program arguments参数(参数之间空格隔开)

基于JAVA反序列化的URLDNS完整利用链分析



02


GeneratePayload简单调试分析


    参数配置完成后,在Utils.getPayloadClass行下个断点进行调试:

基于JAVA反序列化的URLDNS完整利用链分析

    继续跟进,查看。

基于JAVA反序列化的URLDNS完整利用链分析

    发现try{}里的forName()并未反射成功,而在下一步才成功。原因是我们仅仅传入的是类名,并未包含完整包路径。查看下面的测试代码可知,即便调用者在同级目录下,传入类名也是无法反射成功,必须要完整的路径。

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析     回到主题,最后将加载完成的URLDNS类返回,然后下面的newInstance()对URLDNS类加载和实例化(forName加载类、newInstance初始化两者结果相当于平时new对象):

基于JAVA反序列化的URLDNS完整利用链分析

    接着跟进getObject,可见来到URLDNS类获取HashMap对象:

基于JAVA反序列化的URLDNS完整利用链分析

    到这里,先暂停,然后从反序列化过程去分析URLDNS类中getObject方法的编写思路。



03

反序列化过程分析


    在URLDNS文件中,查看其利用链:

基于JAVA反序列化的URLDNS完整利用链分析

    首先回到GeneratePayload中,注释最后的序列化操作,将我们生成的object序列化写入ser文件中:

基于JAVA反序列化的URLDNS完整利用链分析

    代码:

FileOutputStream fo = new FileOutputStream(System.getProperty("user.dir")+"/src/main/java/ysoserial/dnslog.ser");ObjectOutputStream obj_out = new ObjectOutputStream(fo);obj_out.writeObject(object);obj_out.close();

    再反序列化读取自定义生成的dnslog.ser文件:

基于JAVA反序列化的URLDNS完整利用链分析

    断点调试readObject()方法,跟进来到putVal()函数,可见key、value的值分别是我们传入的URL对象、url字符串。

基于JAVA反序列化的URLDNS完整利用链分析

    先找到hash方法(ctrl+左键单击)下断点,再选择F7步入,否则进入的是putVal()而不是hash()。

基于JAVA反序列化的URLDNS完整利用链分析

    因为key不为空,所以执行了(h = key.hashCode()) ^ (h >>> 16),h与其无符号右移16位的值进行异或运算,跟进查看URL#hashCode(),当前类中设定私有变量hashCode值为-1。

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

    继续跟进URLStreamHandler#hashCode():

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

    查看jdk1.8的文档,结合文档说明,了解到这里将触发dns查询(有兴趣可以继续跟进)。

基于JAVA反序列化的URLDNS完整利用链分析

    到这里,也达到了我们的目的,通过反序列化让目标主机被动进行dns查询。



04


编写POC


    既然了解了其反序列化后的触发流程,那其实我们已经可以尝试单独写POC了

基于JAVA反序列化的URLDNS完整利用链分析

    但上面代码会在序列化前就发生dns查询。 

基于JAVA反序列化的URLDNS完整利用链分析

    我们的目的是让它反序列化时触发dns查询操作,所以改改改!put方法里调用putVal,而putVal里又调了hash()

基于JAVA反序列化的URLDNS完整利用链分析

    看到这里是不是想起来了基于JAVA反序列化的URLDNS完整利用链分析,在URL#hashCode()里因为hashCode变量值默认为-1才跳过if语句来到handler.hashCode(),也就是在这里面触发的查询。如果我们修改了hashCode变量值不等于-1,不就可以提前return了吗?基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

    dnslog重新获取一个url,代码修改为:

基于JAVA反序列化的URLDNS完整利用链分析

    运行后,没有访问记录,可见序列化时没有触发dns查询。

基于JAVA反序列化的URLDNS完整利用链分析

    代码还有个问题,URL#hashCode变量的值已经被修改为666,序列化后,这个值也会被序列化。反序列化时因为值不为-1,无法触发dns查询了。问题不大,put()值后把值改回来再序列化。最后完整代码为:

基于JAVA反序列化的URLDNS完整利用链分析


05


URLDNS中getObject分析


    回到我们一开始的URLDNS,先看第一行代码,它的作用是什么?

基于JAVA反序列化的URLDNS完整利用链分析

    URLDNS类中定义了SilentURLStreamHandler内部类继URLStreamHandler,同时重写了 URLStreamHandler父类的getHostAddress方法,直接return null。

    而在前面的反序列化分析中,我们也了解到这个getHostAddress方法是在URLStreamHandler#hashCode()方法中调用,通过它去让目标主机进行dns查询。

基于JAVA反序列化的URLDNS完整利用链分析

    重点来了,通过实例化子类,调用子类的getHostAddress方法,避免调用父类的getHostAddress,完美规避了dns查询。

    这与我们前面修改URL#hashCode属性值的方法大同小异,目的都是防止序列化之前触发dns查询。那么它怎样才能引用到子类的的方法?URL的其中一个构造方法允许我们传入URLStreamHandler对象。

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

    再看put对参数的引用,key就是我们传入的URL对象,value随便传点东西进行,有值就行。

基于JAVA反序列化的URLDNS完整利用链分析

    再看hash(key)

基于JAVA反序列化的URLDNS完整利用链分析

    又回到key.hashCode也就是URL#hashCode,这里handler就是URLDNS#getObject首行new的子类SilentURLStreamHandler对象。

基于JAVA反序列化的URLDNS完整利用链分析

    子类SilentURLStreamHandler并未重写hashCode()方法,因为继承关系,可以调用父类URLStreamHandler方法中的hashCode()。

    然后该方法中调用了getHostAddress方法,因为子类重写了getHostAddress方法,所以会优先执行本身已定义的方法,从而达到避免使用父类getHostAddress的方法。

基于JAVA反序列化的URLDNS完整利用链分析

    返回null,避免了DNS查询。那么,问题又来了,返回空之后进行序列化,那反序列化的时候不也触发不了DNS查询,岂不是完犊子了?

    在URL类中找到这个家伙:

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

整段代码试试水:

package ysoserial.test;
import java.io.*;
public class test2 { public static void main(String[] args) throws IOException, ClassNotFoundException { human person = new human("ch0mie", 18); System.out.println("序列化前:"+person.getAge()); //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir")+"/src/main/java/ysoserial/test/dnslog.ser")); oos.writeObject(person); oos.close();; //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir")+"/src/main/java/ysoserial/test/dnslog.ser")); human hm = (human)ois.readObject(); System.out.println("反序列化后"+hm.getAge()); ois.close(); }}class human implements Serializable{ private String name; transient int age;    public human(String name, int age){ this.name=name; this.age=age; } public String getName(){ return this.name; } public int getAge(){ return this.age;    }}

基于JAVA反序列化的URLDNS完整利用链分析

    我的18岁没了,说明注解类型为transient的属性和方法不会被序列化,即“瞬态”回到这里。

transient URLStreamHandler handler;

    在使用子类SilentURLStreamHandler的getHostAddress方法,handler对象是不会被序列化到数据流中,即对反序列化时DNS查询的触发并不影响。

    真正触发DNS查询还是与URL#hashCode变量的值有关这行代码应该也猜到为什么要写了吧?

基于JAVA反序列化的URLDNS完整利用链分析

    在序列化前,执行到hashCode = handler.hashCode(this);

基于JAVA反序列化的URLDNS完整利用链分析

    再看getHostAddress,这段代码对h值并不影响 ,相当于加0。

基于JAVA反序列化的URLDNS完整利用链分析

基于JAVA反序列化的URLDNS完整利用链分析

    明显,h值最后返回绝不等于-1,此时URL#hashCode变量的值是大于0的,而它的值是可以被序列化的。

    这时反序列化的时候因为URL#hashCode变量的值不等于-1提前return了,也就无法触发dns查询。所以这也是为什么put之后,在序列化前要URL#hashCode变量的值重置为-1。

Reflections.setFieldValue(u, "hashCode", -1);


参考:

https://www.anquanke.com/post/id/208274



06


后记


    这是Fighter的第 20 篇文章。Fighter的背后是一个积极向上、永不言弃的大学生群体。也欢迎更多的小伙伴来加入Fighter,一起交流、成长进步。至此本文对URLDNS利用链的简单分析结束,如有不足之处,望指点纠正。

本文始发于微信公众号(Fighter安全团队):基于JAVA反序列化的URLDNS完整利用链分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: