前言
我们之前已经学习了URLDNS,通过对该利用链的分析,对反序列化的利用又有新的理解,不过相对于CommonCollections反序列化链的分析来说,URLDNS只能算是给我们做一个准备,我们首先对针对CommonCollections1学习分析前进行简单的前置内容学习,让我们在后续分析的时候有更加透彻的了解
关键的接口和类
AbstractMapDecorator
CommonsCollections
库中提供了一个抽象类org.apache.commons.collections.map.AbstractMapDecorato
,这个类是 Map
的扩展,并且从名字中可以知道,这是一个基础的装饰器,用来给 map
提供附加功能,被装饰的 map
存在该类的属性中,并且将所有的操作都转发给这个 map
。这个类有很多实现类,各个类触发的方式不同,重点关注的是 TransformedMap
以及 LazyMap
。
Transformer接口
Transformer
是Apache Commons Collections
库引入的一个接口,每个具体的 Transformer
类必须实现 Transformer
接口,Transformer
只有一个方法transform(),这将是解锁计算器的关键方法。
ConstantTransformer
这个类实现了Transformer
,并且也实现了Serializable
接口,说明是可序列化的。它的作用就是构造函数的时候传入一个对象,在执行回调(transform
方法)时返回这个对象,进而方便后续操作。这个类用于和 ChainedTransformer
配合,将其结果传入 InvokerTransformer
来调用我们指定的类的指定方法
ChainedTransformer
这个同样是实现了Transformer
接口的一个类,同时也实现了Serializable
接口,作用是将内部的多个Transformer
串在一起。ChainedTransformer
的transform
函数中指定了将传入对象按顺序执行transformers
数组中的transform
方法,前一个transformer
的输出作为后一个transformer的输入。通俗来说就是,前一个回调返回的结果,作为后一个回调的参数传入。
那么这样我们可以利用ChainedTransformer
将ConstantTransformer
和InvokerTransformer
的transform
方法串起来。通过ConstantTransformer
返回某个类,交给InvokerTransformer
去调用类中的某个方法。
官方介绍:
将指定的转换器连接在一起的转化器实现。输入的对象将被传递到第一个转化器,转换结果将会输入到第二个转化器,并以此类推
InvokerTransformer
这个实现类从 Commons Collections 3.0
引入,功能是使用反射创建一个新对象,我们来看一下它的 transfrom
方法,方法注释写的很清楚,通过调用 input
的方法,并将方法返回结果作为处理结果进行返回。
在InvokerTransformer
中实现了转变的方法transform
,该方法会在map
被改变的时候调用。在途中红色框中的代码块使用反射的方式,获得转变后的对象的类。然后第二行取得该类的一个方法,方法名(this.iMethodName
)和对应的参数列表(this.iParamTypes
)在构造函数中已经指定。第三行以输入的该对象来执行获得的这个方法,执行的参数(this.iArgs
)也在构造函数中指定。
测试代码
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokerTransformerDemo {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
}
}
TransformedMap
org.apache.commons.collections.map.TransformedMap
类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer
来定义,Transformer
在 TransformedMap
实例化时作为参数传入,Transformer
实现类分别绑定到 map
的 key
和 value
上,当 map
的 key
或 value
被修改时,会调用对应 Transformer
实现类的 transform()
方法。我们主要用这个类中的decorate()来衔接chains。
编写TransformedMap测试
MyTransformer
import org.apache.commons.collections.Transformer;
import java.io.Serializable;
public class MyTransformer implements Transformer, Serializable {
private String name;
//protected修饰的构造方法,给成员变量赋值
protected MyTransformer(String name){
System.out.println("MyTransformer:MyTransformer()");
this.name = name;
}
//使用getInstance获取对象
public static Transformer getInstance(String name){
System.out.println("MyTransformer:getInstance()");
return new MyTransformer(name);
}
//重写transform方法
@Override
public Object transform(Object input) {
System.out.println("MyTransformer:transform()");
System.out.println("input is : " + input);
return this.name;
}
}
测试类
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class MyTransformerDemo {
public static void main(String[] args) {
//获取一个MyTransformer实例
MyTransformer myTransformer = (MyTransformer) MyTransformer.getInstance("trans-value");
//创建map集合
HashMap map = new HashMap();
map.put("key1", "value1");
map.put("key2", "value2");
System.out.println(map);
System.out.println("=========================");
// 将 MyTransformer实例作为 valueTransformer 绑定到 map 上
// 即当 map 的 value 更改时,自动调用 MyTransformer 的 transform 方法
Map transformedMap = TransformedMap.decorate(map, null, myTransformer);
// 获取map中的第一组元素
// 将键值对封装成一个对象, iterator() 获取迭代器 next()获取第一对值
Map.Entry entry = (Map.Entry) transformedMap.entrySet().iterator().next();
System.out.println(entry.toString());
// 更改 map 的 value 来触发 MyTransformer 的 tranform 方法
entry.setValue("newValue");
System.out.println(map); // {key1=trans-value, key2=value2}
}
}
输出结果
Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对),Map.Entry里有相应的getKey和getValue方法,让我们能够从一个项中取出Key和Value。
- Map.Entry的作用
- Map.Entry是为了更方便的输出map键值对。一般情况下,要输出Map中的key 和 value 是先得到key的集合keySet(),然后再迭代(循环)由每个key得到每个value。values()方法是获取集合中的所有值,不包含键,没有对应关系。而Entry可以一次性获得这两个值。
HashMap<String,String> hashMap = new HashMap<String, String>();
hashMap.put("1","aa");
hashMap.put("2","bb");
hashMap.put("3","c");
//方法1,由于二次取值,效率比方法2,方法3慢一倍
for(String key : hashMap.keySet()){
System.out.println(key+"--------"+hashMap.get(key));
}
/**
* Map.entrySet迭代器会生成EntryIterator,其返回的实例是一个包含key/value键值对的对象。
* 而keySet中迭代器返回的只是key对象,还需要到map中二次取值。故entrySet要比keySet快一倍左右。
*/
Iterator<Map.Entry<String,String>> it = hashMap.entrySet().iterator();
while (it.hasNext()){
Map.Entry<String,String> entry = it.next();
System.out.println(entry.getKey()+"--------"+entry.getValue());
}
//第三种:无法在for循环时实现remove等操作
System.out.println("通过Map.entrySet遍历key和value");
for(Map.Entry<String,String> entry : hashMap.entrySet()){
System.out.println("key="+entry.getKey()+" and value="+entry.getValue());
}
Map.entrySet() 将Map集合转换成set集合。Map.entrySet迭代器会生成EntryIterator,其返回的实例是一个包含key/value键值对的对象。而keySet中迭代器返回的只是key对象,还需要到map中二次取值。故entrySet要比keySet快一倍左右。