温馨提示: 本文仅供网络安全技术交流,请勿用于非法用途。所有渗透测试需获得授权,否则后果自负,与本号及作者无关。请务必遵守法律法规!
剧透警告: 阅读本文,你将掌握 Apache Commons Collections (CC) 反序列化漏洞 CC1 调用链的精髓,从此告别“一脸懵逼”状态,走上安全巅峰!
漏洞分析大纲
1. CC1 调用链流程:抽丝剥茧,直击核心
先来一张图镇楼,让你对 CC1 的整体流程有个清晰的认识:
再来一张图,加深印象:
简单来说,CC1 调用链就像一条贪吃蛇,你要做的就是找到它的头和尾:
- S1:找到反序列化入口。 就像发现了一个宝藏,这个入口可以接收你精心构造的序列化数据流。
- S2:寻找序列化入口类。 找到重写了
readObject
方法的类,并且这个方法能够像多米诺骨牌一样,触发一系列的危险操作,最终引爆我们的“炸弹”——执行Runtime.exec("calc")
。
目标明确: 我们的终极目标是让程序执行 Runtime.exec("calc")
,弹出计算器!为了达成这个目标,我们需要逆向追踪,找到能够调用这个危险方法的“幕后黑手”,以及能够调用“幕后黑手”的“黑手老大”,最终找到重写了 readObject
方法的序列化入口类。
友情提示: 上一篇文章已经介绍了 Commons Collections 中一个非常重要的接口—— Transformer
接口,它是我们构建 CC1 调用链的基石。
2. Transformer 接口:化腐朽为神奇的“变形金刚”
(1) 作用:
Transformer
接口就像一个“变形金刚”,接收一个 Object
,然后返回另一个 Object
。它的实现类可以根据你的需求,对输入的 Object
进行各种自定义处理,并返回相应的结果。
CC1 调用链中,我们主要会用到以下几个 Transformer
接口的实现类: InvokerTransformer
、TransformedMap
、AnnotationInvocationHandler
、ConstantTransformer
和 ChainedTransformer
。
3. InvokerTransformer 类:反射的艺术
(1) 作用:
InvokerTransformer
类就像一个“遥控器”,你输入一个对象,它会通过反射的方式,调用你指定的对象的某个方法(方法名、参数类型和参数值都由你来控制)。
关键点: iMethodName
参数是从哪里传入的?
真相只有一个: iMethodName
参数在 InvokerTransformer
类初始化构造的时候就被传入并赋值了!这意味着,我们可以控制执行任意对象的方法!
目标锁定: 让它执行一个危险方法!
(2) 正常情况下(未使用 InvokerTransformer 类)执行危险方法:
import org.junit.Test;
(3) 使用 InvokerTransformer 类执行危险方法:
import org.apache.commons.collections.functors.InvokerTransformer;
代码解读:
new InvokerTransformer
:创建一个InvokerTransformer
对象,需要传入方法名、参数类型列表和参数值列表。exec
:方法名是exec
,也就是我们要执行的命令。Class[] {String.class}
:参数类型列表是一个Class
数组,这里表示exec
方法接收一个字符串类型的参数。new Object[] {"calc"}
:参数值列表是一个Object
数组,这里表示我们要执行的命令是打开计算器。Runtime.getRuntime()
:我们要对Runtime.getRuntime()
对象进行方法调用。
小目标达成: 我们成功地将危险方法向前推进了一步!调用流程变成了:
4. TransformedMap 类:巧妙的“中转站”
接下来,我们需要找到谁调用了 Transformer
方法。
秘籍: 搜索哪里使用了 Transformer
方法!
答案揭晓: TransformedMap
类的 checkSetValue()
函数调用了 Transformer
方法!
精髓: 如果 valueTransformer = invokerTransformer
,并且将 Runtime.getRuntime()
对象传入 checkSetValue()
函数,那么执行效果就等同于 invokerTransformer.transform(Runtime.getRuntime())
!
问题来了: 如何给 valueTransformer
变量赋值?
溯源: valueTransformer
变量是在哪里赋值的?
真相: TransformedMap
方法可以给 valueTransformer
变量赋值,但这个方法的访问级别是 protected,只能在 TransformerMap
类的内部调用。
峰回路转: TransformedMap
类还有一个 decorate
装饰方法,可以将 valueTransformer
变量传入,然后再调用 TransformedMap
方法。decorate
方法是一个公开静态方法,可以进行实例化。
测试代码:
Map decorated = TransformedMap.decorate(null, null, invokerTransformer);
完美: 我们可以调用 TransformedMap.decorate
方法,传入一些 null
值,再把我们要调用的 invokerTransformer
传进去。最后,我们再调用 TransformedMap
类中的 checkSetValue()
函数,并把 Runtime.getRuntime()
对象传进去,就等同于 invokerTransformer.transform(Runtime.getRuntime())
!
但是! checkSetValue()
函数也是受限制的!
继续追踪: 在 TransformedMap
类中,哪里调用了 checkSetValue()
函数?
答案: TransformedMap
类继承的父类 AbstractlnputCheckedMapDecorator
中调用了 checkSetValue()
函数,它是在 MapEntry
实体中调用的。也就是说,在调用 MapEntry
实体的时候,就会调用 checkSetValue()
函数。
当前目标:
- 给
AbstractlnputCheckedMapDecorator$MapEntry.setValue()
函数中传入我们的Runtime.getRuntime()
; - 给
TransformedMap.valueTransformer
赋值成之前构造好的invokerTransformer
对象。
这样就等同于执行了 invokerTransformer.transform(Runtime.getRuntime())
。
再次梳理: checkSetValue()
函数的本质是,每次赋值的时候,它会把你的值替换成通过 Transformer
方法转换之后的输出对象。也就是说,不管是赋值什么,它都会通过 Transformer
方法给你转换一下再存进 Map
里面。
Decorator 模式: decorate(map)
方法传入一个 map
,然后返回给你一个 map
。这个 decorator
对你输入的 map
做了一些增强,添加了一些新的功能,或者在调用原始对象的方法之前或之后执行自己的操作。
具体来说: 被增强后的 TransformedMap
对象,在执行 Map.Entry.setValue()
的时候,会被替换成重写过的 setValue()
函数。
构造方法:
- 先构造一个我们自己的
map
,键和值都是Object
类型; - 在这个
map
里面放入一些值,比如 "a" 和 "b"; - 最后,我们用
TransformedMap
对原始map
做一个增强,keyTransformer
为null
,valueTransformer
使用之前构造好的invokerTransformer
对象。它会返回一个增强过的Map
,它的键和值仍然都是Object
类型。
问题: 如何调用这个增强过的 map
呢?
答案: 使用 for
循环遍历它的实体,拿到实体之后,就可以调用增强之后的 map
了。
代码示例:
import org.apache.commons.collections.Transformer;
可以看到里面的键值对就成了一个实体。
然后我们再通过 entry.SetValue
传入我们的危险方法,即 Runtime.getRuntime()
。
代码如下:
import org.apache.commons.collections.Transformer;
里程碑: 我们把利用链又往前推进了一步!调用流程变成了:
5. AnnotationInvocationHandler 类:终极 Boss 登场!
接下来,我们在 AbstractlnputCheckedMapDecorator
中寻找谁可以调用 Map.Entry.setValue()
函数。
方法依旧: 通过搜索,发现有很多类都调用了 Map.Entry.setValue()
函数。但是,我们最好找到一个序列化入口类,也就是重写了 readObject
方法,并且在 readObject
方法里面调用了 setValue()
函数,这样就可以一步到位,省略了寻找中间类的过程。
Bingo! AnnotationlnvocationHandler
类满足上述所有条件:
- 可以被序列化:
- 重写了
readObject
方法:
- 在
readObject
方法里面调用了setValue()
函数:
至此,我们又向前推进了一步,现在这个链条已经完整了!调用流程变成了:
But! 还存在一些问题:
问题一: 如何让遍历之后的 Map
等于我们增强的 TransformedMap
,也就是如何使 AnnotationlnvocationHandler
类中 memberValues
等于我们的 TransformedMap
对象呢?
答案: 只需查看 AnnotationlnvocationHandler
类中 memberValues
是在哪里开始赋值的。
真相: AnnotationlnvocationHandler
类的构造方法会进行赋值!所以,当我们在构造它的时候,就可以给它传入一个我们增强好的 Map
,那么在它对我们增强好的 Map
进行 setValue
的时候,它就会调用我们链条中的方法。
问题二: AnnotationlnvocationHandler
类不是 public
的,我们无法在外部构造(实例化),该怎么办呢?
答案: 可以使用反射把它构造出来!
具体操作: 我们需要查看它要传入什么参数。第一个参数传入的是一个注解类型,第二个参数就是我们可以给它传入一个增强好的 Map
。
尝试使用反射把这个序列化入口类构造出来:
import org.apache.commons.collections.Transformer;
下一步: 调用 AnnotationlnvocationHandler
类的 readObject
方法。但是,readObject
方法是私有的,所以不能直接调用 o.readObject
。我们知道,在进行反序列化的时候会调用 readOject
方法,所以在反序列化之前,我们还需要序列化一次。因此,我们可以通过调用前面准备好的序列化和反序列化函数(在 CC.java
文件中),并给反序列化函数传入我们序列化之后的文件名。
验证: 在执行这段代码的时候,AnnotationlnvocationHandler
类的 readObject
方法也会被执行起来。我们可以在 AnnotationlnvocationHandler
类的 readObject
方法这里打上断点测试一下。
结果: 成功停在了断点处,并且执行了我们的 readObject
方法!
问题三: 如何确保我们的代码能够执行 setValue()
函数?
分析: setValue()
函数外面有两层 if
判断。只有 if
判断成立,才能执行到 setValue()
函数。
破解:
- 第一层
if
:memberType != null
。从Map<String, Class<?>> memberTypes = annotationType.memberTypes()
可以看出,memberType
又是一个Map
,从memberTypes.get(name)
可以看出,这个函数里面的参数不能为空。那么,我们可以给它传入一个已经存在的key
,这样就不可能为空了。 - 追溯
memberTypes
:memberTypes = annotationType.memberTypes()
,memberTypes
等于一个注解Type
(annotationType
)。 - 追溯
annotationType
:annotationType = AnnotationType.getlnstance(type)
,这个注解Type
就是我们前面构造AnnotationlnvocationHandler
方法的时候传进去的注解Type
。
结论: 这个注解 Type
就是我们自己传入的!
整体流程: 通过我们传入的注解 Type
返回了我们的注解对象,然后我们再调用这个注解对象的 memberTypes
方法。
先看一下调用这个注解对象的 memberTypes
能得到什么东西?
测试代码:
@Test
结果: AnnotationType.getInstance
将我们的注解类 Target
传进去,然后它返回的是一个注解 Type
,然后这个 memberTypes
方法返回的是一个 Map
。
结论: 只要出现键值对,我们就直接把这个键(key
)传给 memberTypes.get()
,它就不可能为空了!
追溯 name
: 从 String name = memberValue.getKey()
和 for(Map.Entry<Object, Object> menberValue:memberValues.entrySet())
可以看到,这个 name
就是我们之前传进去的增强 Map
。
最终结论: 我们会给它传入增强 Map
,然后这个增强 Map
是可控的,它会自动地寻找它的 key
,所以最后 memberTypes.get()
函数我们传入 map.key
,只要这个 key
里面有前面实体(前面测试代码)中键值对的 value
,我们就可以使这两个 if
成立!
接下来开始构造:
import org.apache.commons.collections.Transformer;
验证: 成功停在了断点处,成功地使第一层 if
成立!
第二层 if
: 判断我们传入的 memberType
是否可以进行实例化,也就是测试代码中的实体中的键值对,一般情况下肯定是可以进行实例化的,所以第二层 if
也自然而然成立。
问题四: 我们给 setValue()
函数传入的参数是不可控的,怎么能够让我们可控呢?
答案: 使用 ConstantTransformer
类可以解决!
6. ConstantTransformer 类:点石成金的魔法
ConstantTransformer 类的作用: 不管你传入什么,它都能正常返回 iConstant
常量!
追溯 iConstant
:
真相: 在构造 ConstantTransformer
类的时候就已经传入了!
开始构造:
因此,我们在调用的时候必须传入 Runtime.getRuntime()
,所以在 entry.setValue()
函数中不管传入什么都不重要了,因为它返回的都是一个 iConstant
常量!
代码如下:
import org.apache.commons.collections.Transformer;
也就是说,其实我们之前的:
和现在的:
效果完全一致!但是,最终我们的调用链没了!虽然可以把常量 Transformer
(ConstantTransformer
)传进去了,但是无法调用起后面的那些类,即 InvokerTransformer
类和执行我们的危险方法!
So,接下来该怎么办呢?
当然是使用 ChainedTransformer
类!
7. ChainedTransformer 类:串联一切的终极武器
作用: 它传入一个 transformer
数组,然后链式调用数组里面的这些 transformer
,前一个 transformer
的输出是后一个 transformer
的输入。
开始构造:
在 ChainedTransformer()
函数中传入我们的 transformer
数组,然后把之前构造好的两个常量 transformer
(ConstantTransformer
)复制过来,分别是 ConstantTransformer
和 invokerTransformer
。最后把我们的两个常量 transformer
放到 transformer
数组中。
由于 ChainedTransformer
不管传入什么都返回的特性,你最终的 ChainedTransformer.transformer
传入什么都不重要了,链条也得以执行,最终执行到我们的危险方法!
现在我们开始调用我们的 transformer
,传入什么都行!
然后现在的话我们就开始把我们的 ChainedTransformer
传给我们的增强 Map
,同样将前面构造好的增强 Map
复制过来,将 ChainedTransformer
传入进去。
最后我们通过反射把我 decorated.map
(装饰好的 map
)复制过来。
代码如下:
import org.apache.commons.collections.Transformer;
执行程序:
What?报错了?!为什么?以及该怎么调用呢?
答案将在交流群里揭晓!
最后献上完整简化代码:
import org.apache.commons.collections.Transformer;
执行程序:
成功弹出计算器!
黑客/网络安全学习包
资料目录
-
成长路线图&学习规划
-
配套视频教程
-
SRC&黑客文籍
-
护网行动资料
-
黑客必读书单
-
面试题合集
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
1.成长路线图&学习规划
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
2.视频教程
很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩。
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
3.SRC&黑客文籍
大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录
SRC技术文籍:
黑客资料由于是敏感资源,这里不能直接展示哦!
4.护网行动资料
其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!
5.黑客必读书单
**
**
6.面试题合集
当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。
更多内容为防止和谐,可以扫描获取~
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*********************************