Commons-Collections反序列化
Java反序列化漏洞
Commons Collections
Apache Commons 是 Apache 软件基金会的项目。Commons Collections 包为 Java 标准的 Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Commons Collections的版本是3.2.1,版本过高就无法调试;还有一点就是Commons Collections其实有俩条利用链,lazymap和transformeredmap。现在网上流行的都是对transformeredmap的分析,就是因为它比较简单利用
关于高版本的jdk调用链其实还有办法
小白可能遇到的问题
作者是一个小白,第一次挖Commons-Collections1这一条链子,遇到了一些困难。在这里做一下标记,希望能够帮助小伙伴们解决相同的问题。
-
寻找jdk版本:Commons-Collections1使用的jdk版本必须是jdk1.8.0_8u71以下,所以我们要适配该版本以下的版本。网上我找了半天没有找到符合要求的jdk,最后看了这位师傅的博客Oracle jdk old release 历史版本下载找到了正确的版本。jdk要下载JDK(JAVA Developpment Kit)
-
关于windows电脑放入多个jdk的问题:因为我们经常使用不同的jdk版本,解决方法是比如说jdk1.7先在虚拟机中安装好,然后将jdk1.7拷贝出来放到主机的java的jdk目录下
-
JDk源码问题:在Oracle JDK中下载的JDK未必包含我们所需要的部分源码,比如说sun包下的源码就不包含。这就导致我们在调试的时候不方便,所以我们需要在openjdk中下载比较接近Oracle JDK版本的源码。这里提供一个CC1链需要的sun包源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
-
IDEA中导入JDK源码:这个就直接看网上的教程了,
-
IDEA调试的快捷键:
- 查看接口的继承子类:ctrl+H
- 查看方法可以被哪里调用:右键选择FindUsages
知识补充:jdk中的包
- java.*
JavaSE的标准库,是java标准的一部分,是对外承诺的java开发接口,通常要保持向后兼容,一般不会轻易修改。包括其他厂家(IBMJDK/HPJDK/OpenJDK)在内,所有jdk的实现,在java.*上都是一样的。 - javax.*
也是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。
此上两者都属于java标准库,公有的API,遵循java平台规范,
- com.sun.*
是sun的hotspot虚拟机中java.* 和javax.*的实现类。因为包含在rt中,所以我们也可以调用。但是因为不是sun对外公开承诺的接口,所以根据根据实现的需要随时增减,因此在不同版本的hotspot中可能是不同的,而且在其他的jdk实现中是没有的,调用这些类,可能不会向后兼容,所以一般不推荐使用。 - org.*
是由企业或者组织提供的java类库,大部分不是sun公司提供的,同com.sun.*,不具备向后兼容性,会根据需要随时增减。其中比较常用的是w3c提供的对XML、网页、服务器的类和接口 - sun.*包:
1、不是API公开接口的一部分,调用sun包的程序并不能确保工作在所有Java平台上,不同的操作系统中的实现可能不相同。
2、不同的jdk版本sun包中的类也可能不定期的变化,因此sun.*包中的类没有提供API文档及源码。
白日梦组长的利用链导图
最后还有一些利用链没贴
p神知识星球里一位师傅对CC链的整理
CC1-TransformMap
版本限制:
jdk版本:低于jdk1.8.0_8u71
commons-collections
版本:
环境配置
我们需要做的工作是下载一个版本低于jdk1.8.0_8u71的jdk,然后在IDEA中新键一个maven项目(不需要配置javaweb项目)。在项目中的pom.xml中进行导入commons-collections的包。
然后我们需要在IDEA中配置jdk的版本,我这里配置的是jdk1.8.0_8u65.
利用链分析
java反序列化漏洞的入口类重写了readObject(),然后通过readObject()方法中调用了其他类的方法,以此类推最后可以执行我们的Runtime类的exec危险方法。分析过程我们从执行Runtime类的exec()方法和InvokeTransformerhander反向分析,最后得出完整的整条利用链。
InvokeTransformerhander
InvokerTransformerHander类中的transform方法是一个危险的方法,分析一下它的方法内容,不难发现是用反射来执行一个方法
用UsagesFind找一下哪里可以调用这个类的transform方法。TransformedMap类的checkSetValue能够调用InvokeTransformerhander的transform方法
继续UsagesFind寻找,AbstractInputCheckedMapDecoator类的setValue()可以调用这个方法
而AnnoationInvocationHander类的readObject方法里面可以调用setvalue方法
因为AnnoationInvocationHander类的构造方法是default,所以我们需要利用反射来构造该类的对象。这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使 用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化 了。
按照上面的思路最后就构造出来这条链子了,但是它是无法进行序列化的。
- Runtime这个类没有继承Serializable,无法序列化
- AnnotationInvocationHandler的readObject方法的俩个if判断以及最后的可控参数setValue()
Runtime类反射
Runtime无法序列化,但是我们知道它的Class类对象是可以序列化的,那么就需要将Runtime命令执行利用反射写出来
但是如果把它放在InvokerTransformer的getInstance中能实现吗?不能实现。但是Commons-Collections框架中有一个ChainedTransformer类transformer方法可以循环利用反射调用方法
可以利用它来做加强版的“InvokeTransformer”,而且它的构造方法也比较简单
所以我们直接构造一个InvokerTransformer数组
但是需要注意一点这里ChainedTransformer的transformer方法的object参数必须是一个承接一个,所以我们最后优化后的是:
setValue参数
我们需要绕过俩个if,因为涉及到注解的一些知识,这里直接给出条件
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
- 被 TransformedMap修饰的Map中必须有一个键名为X的元素
这里选择Target注解,所以键名是value
这个问题解决完以后,还有就是setValue()方法的参数。它的参数其实是不可控的,Debug调试一下,当我们走到ChainedTransformer的transform方法时,看到的第一个Object参数,永远不可控。
但是Commons-Collections有一个类是ConstantTransformer,它的transformer参数无论接受什么都会返回iConstant。这对我们来说非常好,并且iConstant参数极其易控制
所以我们最后构造的Transform数组是:
整体的思路:
整理一下,最后完整的POC是:
CC1-LazyMap
版本限制:
jdk
版本:jdk8u71
以下
commons-collections
版本:
CC1有俩条链,这一条链后半部分与前面的那条链后半部分相同,区别在于LazyMap替换了TransformedMap,ChainedTransformer的transform方法通过FindUsages查询发现也可以在LazyMap中找到,并且factory也非常好控制
这一条链涉及到了注解和动态代理的知识,比前一条CC1链难理解,这里直接给出Gadget构造思路(以后再进行补充)
Gadget构造思路:
LazyMap的利用链:
CC6-最好的链
版本限制:
jdk版本限制:jdk7和jdk8不受限制
commons-collections
:对commons-collections
版本限制不大
在前面说了分析了CommonsCollections1这个利⽤链和其中的LazyMap原理。但是我们说到,在 Java 8u71以后,这个利⽤链不能再利⽤了,主要原因 是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。也因为这一次改变导致整个在AnnotationInvocationHandler做为入口类的链都不能用了。CC6这一条链是ysoserial里常用的一条链,该利用链可以在java 7和8的高版本触发,没有版本限制,说实话其实CC6也不会受Commons-Collections的版本限制
这一条链还是调用了CC1的LazyMap开始的后半部分,我们从后面开始分析。
如果有个函数的方法可以调用LazyMap的get方法就可以实现漏洞利用,于是我们找到了TiedMapEntry类的hashCode方法,hashCode方法可以调用getValue()方法,getValue()则会调用get方法
通过寻找我们发现HashMap的readObject()方法调用了hash方法而hash方法里又可以调用TiedMapEntry的hashCode方法,这就很方便了。同时你如果知道URLDNS的话,会发现其中我们需要做一些处理(规避)。
如果我们仅仅是将其这样设计,发现它直接就会弹计算器,如果我们跟进hashMap0.put()会发现它会调用lazyMap的hashCode(),所以会调用计算器,所以我们要规避处理。想办法让它不弹出计算器,通过反射改变链的随便一部分都可以,因为我们有ConstantTransformer
。这里我们让LazyMap的decorate先修饰ConstantTransformer,最后通过反射来更改它为Chainedtransformer
但是又有一个新的问题,反序列化依然无法弹出计算器。什么原因呢?这一次的问题出现在lazymap的get方法处,这里还是承接了hashMap0.put()
带来的影响,详细变化如下:
这样的话hashMap的key中就会在反序列化之前提前有templatesImpl,真正反序列化的时候if (map.containsKey(key) == false)
就会判断为真,链子将无法执行。
当我们准备序列化时,走到lazyMap的get()方法时if条件判断为真就会走入get()方法,然后当反序列化的时候map的key已经存在了if判断条件为假就不会再执行if的部分了
所以说我们要在反序列化之前把key值消掉,HashMap的remove()方法可以去掉HashMap的key值所以最后整理的代码是这样:
这样就可以成功在反序列化的时候成功弹出计算器了,
整理思路
最后真正的Poc如下:
CC3-代码执行
版本限制:
jdk版本:依旧编写方式而定
commons-collections
:
解释一下这里为什么说jdk版本要按照编写方式而定:CC3本质上只是更改了ChainedTransformer内部的类,把命令执行更换成了代码执行,前面的部分没有发生变化,所以说如果按照CC1的方式写会限制jdk的版本在jdk8u71以下,而按照CC3的方式写就没有jdk7和8版本的限制。
ysoserial
中的CC3链与前面说的CC1和CC2以及CC6不同,前面的是命令执行,而这一条链则是代码执行。CC3这一条链使用的是动态类加载(动态加载字节码)的方式,我们利用动态类加载的加载器是java.lang.ClassLoader
,使用这一类加载器会顺序执行3个方法来进行类加载:loadClass()
,findClass()
,defineClass()
但是由于java.lang.ClassLoader
类的defineClass()
方法是protected
的,所以我们需要寻找重写defineClass()
且作用域是public
的类,这里我们找到的是TemplateImpl
类,
CC3-方式1
CC3的前半部分是CC1(ysoserial
)readObject()
至InvokerTransformer
,后半段是TemplateImpl
类加载利用链;这条利用链的动态类加载部分在《java反序列化漏洞入门篇》有过记录;这条链的前半部分就是CC1的前半部分。
整体思路:
完整的POC:
CC3-方式2(ysoserial)
ysoserial
的代码,会发现CommonsCollections3
和上述代码并不同,没有使⽤到InvokerTransformer
。原因是因为:2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及Java反序列化利⽤⼯具ysoserial,随后引爆了安全界。开发者们⾃然会去找寻⼀种安 全的过滤⽅法,于是类似SerialKiller这样的⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的类。在SerialKiller
中就有过滤InvokerTransformer
。针对此情况ysoserial
的CC3变换InvokerTransformer
为InstantiateTransformer
同时使用了TrAXFilter
整理一下这后半段的思路:
现在我们需寻找哪个类的方法调用了newTransformer()
,Find Usages
一下发现在TrAXFilter
类的构造方法直接可以调用TemplatesImpl
的newTransformer()
。
但是我们可以注意到TrAXFilter
这个类是不能进行序列化的,但是它的类是可以序列化的,同时我们可以在commons-collections
中找到这样一个类:InstantiateTransformer
,它的transform方法可以实例化一个类
所以我们整体的思路就出来了
ysoserial整体思路:
完整的POC:
运行结果:
Commons-Collections的变动
背景:
在 2015 年底 commons-collections 反序列化利用链被提出时,Apache Commons Collections 有以下两个分支版本:
- commons-collections:commons-collections
- org.apache.commons:commons-collections4
可⻅,groupId 和 artifactId 都变了。前者是 Commons Collections 老的版本包,当时版本号是3.2.1,后者是官方在 2013 年推出的 4 版本,当时版本号是 4.0。
官方认为旧的 commons-collections 有一些架构和 API 设计上的问题,但修复这些问题,会产生大量不能向前兼容的改动。所以,commons-collections4 不再认为是一个用来替换 commons-collections 的新版本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。
那么,既然 3.2.1 中存在反序列化利用链,那么 4.0 版本是否存在呢?
内部包的改动:
前后变化的比较大,俩个版本的库是能够共存的,放在pom.xml
中比较一下
用之前的 CommonsCollections6
利用链做个例子,然后将所有 import org.apache.commons.collections.*
改成 import org.apache.commons.collections4.*
。
PriorityQueue利用链
ysoserial
还为 commons-collections4 准备了两条新的利用链,那就是 CommonsCollections2
和 CommonsCollections4
。
commons-collections 这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其中包含了一些可以执行任意方法的 Transformer。所以,在 commons-collections 中找 Gadget 的过程,实际上可以简化为,找一条从 Serializable#readObject() 方法到 Transformer#transform() 方法的调用链。transform
后面的部分就是我们熟悉的命令执行或代码执行。
CC2
利用链分析:
在 CC2 中,用到的两个关键类是:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
PriorityQueue
优先队列(Priority Queue):java中基于二叉推实现。正常进队,按照优先级出的队列,优先级由comparator来决定。
java.util.PriorityQueue
类重写了 readObject()
方法:
org.apache.commons.collections4.comparators.TransformingComparator
中的compare
调用 transform()
方法的函数:
接下来看下这个 Gadget 的串联方式: PriorityQueue#readObject()
中调用了 heapify()
方法, heapify()
中调用了 siftDown()
, siftDown()
中调用 siftDownUsingComparator()
, siftDownUsingComparator()
中调用的 comparator.compare()
,于是就连接到上面的 TransformingComparator
了:
总结下来就是:
当优先队列(Priority Queue)反序列化的时候调用heapify()
方法恢复二叉推的结构,将数组里面的元素按优先级排列。排列的时候会涉及到优先级的比较,这个时候就会调用对应的compare()
方法
java.util.PriorityQueue
是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树。- 反序列化时调用
heapify()
方法,是为了反序列化后,需要恢复这个结构的顺序。 - 排序是靠将大的元素下移实现的。
siftDown()
是将节点下移的函数, 而comparator.compare()
用来比较两个元素大小。 TransformingComparator
实现了java.util.Comparator
接口,这个接口用于定义两个对象如何进行比较。siftDownUsingComparator()
中就使用这个接口的compare()
方法比较树的节点。
CC2这一条利用的是:InvokerTransformer
无数组的代码执行。
整体思路:
完整POC:
CC4
版本限制:
jdk版本:jdk7,8没有限制
commons-collections:
背景:
在CC2的利用背景下,改进 PriorityQueue 利用链:因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。所以便有了 CC4,CC4 只是将 CC2 中的 InvokerTransformer 替换为了 InstantiateTransformer。
利用链分析:
首先需要明白:如果要getshell:1.命令执行,2.代码执行。CC4的链虽说是引入了新的commons-collections包,但是整体的思路还是没有发生变化,后半部分可以使用命令执行(Runtime
类)或者代码执行(Templatelmpl
调用链)。在ysoserial
中使用的是无InvokerTransformer
的代码执行。我按照ysoserial
的利用链模式来分析。
所以直接对transform
方法开始FindUsages
,TransformingComparator
的compare()
方法可以调用chainedTransformer
的transform()
对compare
方法进行FindUsages
可以找到PriorityQueue
类的readObject()
方法里调用heapify()
而heapify()
方法里调用了siftDown()
,siftDown()
方法里调用了siftDownUsingComparator(k, x)
,其又可以调用TransformingComparator
类的compare
方法
所以整条利用链就可以这样写:
结果就是序列化和反序列化都没有弹出计算器。我们debug
一下找错误,既然反序列化是从readObject()
开始,我们就从PriorityQueue
的readObjct()
开始打断点,问题出现在了:
我们需要进行priorityQueue.add(1)
俩次才可以解决问题,(因为这里涉及到数据结构,但我没有学过,所以这里做个标记)。所以整理后的利用链如下:
结果我们发现序列化和反序列化都可以弹出计算器,跟进priorityQueue.add()
发现其最终会调用transform()
方法,和前面的CC6一样,我们首先需要将其断开,再add()
以后利用反射重新将其连接即可。
利用链思路:
完整POC:
运行结果:
参考链接
__EOF__

本文链接:https://www.cnblogs.com/BUTLER/p/16478574.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具