温馨提示: 本文仅供网络安全技术交流,请勿用于非法用途。所有渗透测试需获得授权,否则后果自负,与本号及作者无关。请务必遵守法律法规!
剧透警告: 阅读本文,你将掌握 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&黑客文籍
-
护网行动资料
-
黑客必读书单
-
面试题合集
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
1.成长路线图&学习规划
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
2.视频教程
很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************
3.SRC&黑客文籍
大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录
SRC技术文籍:

黑客资料由于是敏感资源,这里不能直接展示哦!
4.护网行动资料
其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!
5.黑客必读书单
**

**
6.面试题合集
当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。

更多内容为防止和谐,可以扫描获取~

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*********************************
1548

被折叠的 条评论
为什么被折叠?



