ysoserial – Clojure分析

0x00 前言

最近一直在研究Java安全,从ysoserial看起,发现Clojure这个Gadget链网上并没有分析文章,所以对这条攻击链进行了简单的分析,动态调试过程中由于计算器一直在弹,所以顺着动态分析的内容静态跟了许久,最后总算把这条攻击链给理顺了。

简单介绍Clojure这个jar包,Clojure是一种运行在Java平台上的 Lisp 方言,Lisp是一种以表达性和功能强大著称的编程语言,但人们通常认为它不太适合应用于一般情况,而Clojure的出现彻底改变了这一现状。如今,在任何具备 Java 虚拟机的地方,您都可以利用 Lisp 的强大功能。通俗点来讲Clojure这个库其实就等同于一个沙箱,沙箱里可以运行各种代码,与Flask中的Jinja有着异曲同工之妙。

这里直接弹计算器,没有任何难点,下面开始分析序列化的构造,看看返回的Object包含什么。

0x01 序列化分析

序列化代码也不是很长,这里的53行和56行被我注释了,同样能够弹计算器,事实上跟进这条链,会发现所有的invoke函数几乎都被重写为赋值函数,其实是比较简单的逻辑,其中47行则是Clojure的shell语言,通过构造这样的代码,就能够在Clojure这个语言机制下去调用shell命令,从而执行代码。

最终的Clojure payload如下图所示,那么之后反序列化的思路就比较简单了,只要最后通过readobejct进入到Clojure的类中,然后通过调用执行这个payload即可实现反序列化。

进入到52行,比较简单,定义了一个HashMap的对象,对象名为fnMap,然后定义了一个AbstractTableModel$ff19274a的对象,对象名为model,接着定义了一个targetMap对象,接着将model对象放入targetMap中去,键名为model,值为null,这里都是初始化部分,不再赘述。

进入到61行,开始跟进函数分析,首先看63行代码,跟进main$eval_opt函数,可以看到构造函数没有任何代码,所以这里看做是一个main$eval_opt的定义函数即可

继续看64行,这里传入设定的payload到var1,跟进invokeStatic函数发现调用了core$constantly$fn__4614,继续跟进会发现是一个初始化函数

最后来看62行代码,跟进core$comp的invoke函数,发现对刚才定义的两个对象进行了赋值,然后传入了invokeStatic函数,但是这里对传入的两个对象进行了位置交换,进入到core$comp$fn__4727函数,其实也是一个简单的构造函数

最后来看65行代码,这里简单讲就是通过PersistentArrayMap将先前的fnMap对象转化了对象属性,以方便进行__initClojureFnMappings的调用,通过调用该函数为model对象进行了对象命名,命名对象为fnMap

那么最后来看看targetMap到底有哪些内容,其中targetMap的键为model对象,而model对象的__clojureFnMap设置为fnMap,fnMap中键为hashCode,值为刚才通过构造函数命令的g和f属性,这里对照着前文看可能比较好,口述着看的确比较复杂。

0x02 反序列化分析

通过上述的构造,下面开始分析反序列化的内容,其实ysoserial也给出了响应的Gadget链,但是跟进到最后发现利用链并不完整,没有给出最关键的执行命令的语句,这也是本文研究的初衷,下面开始动态调试,看看如何进行反序列化调用。

首先targetMap为HashMap对象,所以会利用HashMap的readobejct函数,同时HashMap继承了Serializable,所以是可以反序列化的

这里继续跟进hash函数

继续跟进hashCode函数

这里给出调用堆栈图和当前变量信息,根据序列化章节,我们知道targetMap中设定了__clojureFnMap属性,那么这里通过RT.get函数取出其中键为hashCode的值,然后赋值给var10000,这里的变量信息其实与先前序列化章节的变量时一致对应的。

这里继续调用core$comp$fn__4727类中的invoke函数,继续跟进看看,最后跟进61行,也就是core$constantly$fn__4614的invoke函数,返回this属性中的x变量,也就是先前构造的payload,接着调用main$eval_opt函数的invoke函数

这里传入的var1也就是g中的x对象,然后调用invokeStatic,继续跟进

在这里传入我们最终的payload,然后经过一系列判断后最终进入到了eval函数中,这是clojure的核心运算函数,通过这个函数来调用shell去执行命令,至此分析结束。

0x03 后记

由于在动态调试过程中一直在弹计算机,可能是在调试过程中引起了报错导致一直出现问题,最后部分基本靠静态分析去做的推理,可能不太准确,相较于ysoserial给出的Gadget链,作了更加细致的分析,上述如有不当之处敬请指出~